├── .gitignore ├── LICENSE.md ├── README.md ├── css ├── bootstrap.min.css ├── reset.css └── style.css ├── editor.html ├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.svg ├── glyphicons-halflings-regular.ttf ├── glyphicons-halflings-regular.woff └── glyphicons-halflings-regular.woff2 ├── js ├── BMPImageLoader.js ├── Body.js ├── GameView.js ├── PhysicsEditor.js ├── PolygonHelper.js ├── SceneManager.js ├── UIManager.js ├── Viewport.js ├── bootstrap.min.js ├── box2d.js ├── hull.js ├── jquery.js └── main.js └── resources ├── autotrace.png ├── autotrace_scene.json ├── editor.png ├── editor_small.png ├── level.jpg ├── loaders ├── Box2dWeb │ └── WorldLoader.js ├── Cocos2d-x │ ├── Box2dWorldLoader.cpp │ ├── Box2dWorldLoader.h │ ├── b2DebugDraw.cpp │ ├── b2DebugDraw.h │ ├── b2DebugLayer.cpp │ └── b2DebugLayer.h ├── LibGdx │ └── WorldLoader.java ├── Sprite Kit │ ├── WorldLoader.h │ └── WorldLoader.m ├── Unity3D │ ├── DebugRenderer.cs │ ├── Fixture.cs │ ├── SimpleJSON.cs │ └── WorldLoader.cs └── file_structure.json ├── monkey.jpg ├── nazca_monkey.json ├── pika.bmp ├── pika.png ├── ragdoll.json ├── scene_1.json ├── scene_3.json ├── title_icon.png └── ui ├── crossair_blue.png ├── crossair_red.png ├── crossair_white.png └── pivot.png /.gitignore: -------------------------------------------------------------------------------- 1 | js/SceneHistory.js -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2015 Amit Kumar Mehar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Physics Editor 2 | ============== 3 | 4 | Physics Editor is a lightweight, browser-based Box2d physics world editor and simulator 5 | 6 | ![editor_screen](resources/editor_small.png) 7 | 8 | Features 9 | -------- 10 | 11 | * Uses [Box2d](http://box2d.org/) for physics simulation 12 | * Easy to use interface and visualization of box2d world 13 | * Creating and exporting entire 2d scene, bodies or shapes 14 | * Graphical user interface to create and edit bodies, shapes and joints 15 | * Support for concave shapes 16 | * Auto trace to generate shapes from 24-bit bitmap image 17 | * Supports javascript console to edit scene using custom scripts 18 | 19 | Demo 20 | ----- 21 | 22 | [Demo Link](http://amutbkt.github.io/Physics-Editor/editor.html) 23 | 24 | Usage 25 | ----- 26 | 27 | editor.html launches the editor 28 | 29 | #### Editor 30 | 31 | Editor object drives the entire editor 32 | ```javascript 33 | Editor.viewport; // handles all the canvas events and rendering 34 | Editor.sceneManager; // handles selecting and editing objects 35 | Editor.uiManager; // handles dom events 36 | Editor.gameView; // handles physics simulation 37 | 38 | Editor.resourceDirectory = "directory containing textures"; // defaults to ./resources 39 | 40 | // auto trace image shape generation paramters 41 | Editor.autoTrace = { 42 | xSpace : 1.0, 43 | ySpace : 1.0, 44 | concavity : 20 45 | }; 46 | 47 | Editor.getCurrentSelection(); // returns an array of selected objects 48 | Editor.getSelectedBodies(); // returns an array of selected bodies 49 | Editor.getSelectedShapes(); // returns an array of selected shapes 50 | Editor.getSelectedVertices(); // returns an array of selected vertices 51 | Editor.getSelectedJoints(); // returns an array of selected joints 52 | ``` 53 | Sample scenes are provided in the ./resources directory 54 | 55 | #### Body 56 | Equivalent to b2Body from Box2d 57 | 58 | #### Shape 59 | Equivalent to b2Shape from Box2d 60 | Shapes can be created only when editing a body 61 | ```javascript 62 | // shape types 63 | Shape.SHAPE_BOX // rectangle shape, vertices cannot be edited, equivalent to b2PolygonShape.setAsBox 64 | Shape.SHAPE_CIRCLE // cicle shape, vertices cannot be edited, equivalent to b2CircleShape 65 | Shape.SHAPE_POLYGON // vertices can be edited, equivalent to b2PolygonShape.set([vertices]) 66 | Shape.SHAPE_CHAIN // vertices can be edited, equivalent to b2ChainShape 67 | ```` 68 | 69 | #### Vertex 70 | Equivalent to b2Vec2 with an exception that it stores data as array [pos_x, pos_y] 71 | 72 | #### Joint 73 | Equivalent to b2Joint 74 | To create a joint, select 2 bodies 75 | ```javascript 76 | // joint types 77 | Joint.JOINT_DISTANCE // fixed distance between bodies 78 | Joint.JOINT_WELD // bodies are glued to each other 79 | Joint.JOINT_REVOLUTE // bodies can rotate about localAnchorB 80 | Joint.JOINT_WHEEL // wheel - axle joint 81 | Joint.JOINT_PULLEY // bodies suspended from pulley 82 | Joint.JOINT_GEAR // a body can drive another body using either revolute/prismatic joint 83 | Joint.JOINT_PRISMATIC // a body's translation can be constrained along an axis (localAxis) 84 | Joint.JOINT_ROPE // distance between two bodies can be constrained 85 | ```` 86 | How to create joints: 87 | * To create distance joint, select two bodies -> create distance joint 88 | * To create weld joint, select two bodies -> create weld joint 89 | * To create revolute joint, select two bodies -> create revolute joint 90 | * To create wheel joint, select two bodies -> create wheel joint 91 | * To create pulley joint, select two bodies -> create pulley joint 92 | * To create gear joint, select two bodies and either two revolute or two prismatic joints or one revolute and one prismatic joint -> create gear joint 93 | * To create prismatic joint, select two bodies -> create prismatic joint 94 | * To create rope joint, select two bodies -> create rope joint 95 | 96 | #### PolygonHelper 97 | [Mark Bayazit's Algorithm](http://mpen.ca/406/bayazit) is used to decompose concave shapes. Concave shape is decomposed to array of convex shapes, as Box2d supports only convex shapes 98 | 99 | #### Auto Trace 100 | [Concave Hull generation](https://github.com/AndriiHeonia/hull) to create shapes from bitmap image easily. To use auto trace, select "Shape From Bitmap" from "Create Shape" dropdown. Image should be grayscale with shape blocked in white color and background (area to be clipped) in black. Sample image (pika.bmp) is provided in the /resources folder. 101 | 102 | ![autotrace_screen](resources/autotrace.png) 103 | 104 | #### Loading Scene 105 | You can export scene created in editor as json file (structure for the same is available in /resources/loaders folder), which can then be loaded in game engine. Currently there are loaders available for [LibGdx](http://libgdx.badlogicgames.com/)(Java), Box2d-Web(Javascript), Cocos2d-x (c++), Apple's Sprite Kit (objective-c) and Unity3D in /resources/loaders folder 106 | ##### To use Cocos2d-x loader: 107 | ```cpp 108 | // make sure to add your resources folder (folder containing json file) to cocos2dx search path 109 | cocos2d::FileUtils::getInstance()->addSearchPath("your_folder"); 110 | 111 | Box2dWorldLoader *loader = new Box2dWorldLoader(); 112 | loader->setOffset(x, y); // to translate world 113 | loader->loadJsonScene("file.json", b2world*); 114 | 115 | // to debug draw physics world just add an instance of b2DegugLayer to your current scene's layer 116 | yourNodeOrLayer->addChild(b2DebugLayer::create(b2world*, pixel_to_meter_ratio), ZOrder); 117 | ```` 118 | #####Example Projects 119 | [Unity Sample Project](https://github.com/amuTBKT/Unity-PhysicsWorldLoader) 120 | 121 | [SpriteKit Sample Project](https://github.com/amuTBKT/SpriteKit-PhysicsWorldLoader) 122 | 123 | UI 124 | -- 125 | 126 | #### Navigation 127 | 128 | * Left Click drag to select multiple objects 129 | * Right Click drag to pan around 130 | * Scroll Wheel to zoom in and out 131 | 132 | #### Hot Keys 133 | 134 | * Shift + D to duplicate selection 135 | * Delete to delete selection 136 | * w to select translate tool 137 | * r to select scale tool 138 | * e to select rotate tool 139 | * s to toggle snapping to grid 140 | * j while selecting to mask bodies 141 | * b while selecting to mask joints 142 | * Ctrl + Left Click to add vertex to a shape while editing 143 | 144 | ##### Editing Joints: 145 | 146 | Weld Joint 147 | * Shift + Left Click drag on bodyA while in edit mode to change reference angle 148 | 149 | Revolute Joint 150 | * Shift + Left Click drag on bodyA while in edit mode to change reference angle 151 | * Shift + Left Click drag on bodyB while in edit mode to change upper angle limit 152 | * Left Click drag on bodyB while in edit mode to change lower angle limit 153 | 154 | Wheel Joint 155 | * Shift + Left Click drag on bodyA while in edit mode to change localAxisA 156 | 157 | Prismatic Joint 158 | * Left Click drag on bodyA while in edit mode to change localAxisA 159 | * Shift + Left Click drag on bodyA while in edit mode to change reference angle 160 | * Shift + Left Click drag on bodyB while in edit mode to change upper translation limit 161 | * Left Click drag on bodyB while in edit mode to change lower translation limit 162 | 163 | Scripting 164 | --------- 165 | 166 | Editor can utilize javascript console to edit scene through scripts 167 | ```javascript 168 | // an example to make copies of selected body 169 | // select a body to clone 170 | // creating a circular pattern here 171 | var radius = 200, resolution = 10; 172 | for (var i = 0; i < resolution; i++){ 173 | // clone the selected body 174 | var b = Editor.getCurrentSelection()[0].clone(); 175 | // set position of the body created 176 | b.setPosition(radius * Math.cos(i * 2 * Math.PI / resolution), radius * Math.sin(i * 2 * Math.PI / resolution)); 177 | // add body to the scene 178 | Editor.getSceneManager().addBody(b); 179 | } 180 | ``` 181 | 182 | Issues 183 | ------ 184 | 185 | * Wheel and Rope Joints are not supported in Box2d-Web, but works with the LibGDX loader 186 | * Undo/Redo not available 187 | * Unstable on firefox 188 | 189 | License 190 | ------- 191 | 192 | Physics Editor is available under MIT license 193 | -------------------------------------------------------------------------------- /css/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | #canvas { 2 | position: absolute; 3 | left: 5%; 4 | top: 10%; 5 | } 6 | 7 | #toolbar{ 8 | position: absolute; 9 | top: 10%; 10 | width: 5%; 11 | height: 80%; 12 | margin: 0 0; 13 | } 14 | 15 | #toolbar button{ 16 | width: 75%; 17 | height: 12.5%; 18 | margin: 7.5% 7.5%; 19 | } 20 | 21 | #taskbar{ 22 | position: absolute; 23 | top: 2%; 24 | left: 2%; 25 | width: 90%; 26 | } 27 | 28 | #taskbar .dropdown{ 29 | float: left; 30 | width: 8%; 31 | } 32 | 33 | #taskbar .dropdown button{ 34 | width: 90%; 35 | } 36 | 37 | #xyInput{ 38 | position:absolute; 39 | top:95%; 40 | left:70%; 41 | width: 15%; 42 | } 43 | 44 | #xyInput .input-group{ 45 | width: 50%; 46 | float: left; 47 | } 48 | 49 | #gameplayControls{ 50 | position: absolute; 51 | left: 40%; 52 | top: 2%; 53 | } 54 | 55 | #selection_properties{ 56 | position: absolute; 57 | left: 85%; 58 | top:10%; 59 | width: 15%; 60 | height: 80%; 61 | overflow: auto; 62 | } 63 | #selection_properties table{ 64 | margin: 10px 10px; 65 | } 66 | #selection_properties td{ 67 | margin: 10px 10px; 68 | padding: 10px 10px; 69 | } 70 | #selection_properties input{ 71 | width: 68%; 72 | } 73 | 74 | #alert_dialog{ 75 | position: absolute; 76 | width: 25%; 77 | top: 35%; 78 | left: 35%; 79 | } 80 | 81 | #alert_dialog button{ 82 | width: 20%; 83 | margin: 5% 6.5%; 84 | } 85 | 86 | #auto_shape{ 87 | position: absolute; 88 | width: 25%; 89 | top: 35%; 90 | left: 35%; 91 | } 92 | 93 | #auto_shape button{ 94 | width: 30%; 95 | margin: 5% 10%; 96 | } -------------------------------------------------------------------------------- /editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Physics-Editor 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 85 | 86 |
87 |
88 | 90 | 92 | 94 |
95 | 96 |
97 | 99 | 101 |
102 | 103 |
104 | 106 | 108 | 110 |
111 |
112 | 113 |
114 | 116 | 118 | 120 |
121 | 122 |
123 |
124 | x 125 | 126 |
127 |
128 | y 129 | 130 |
131 |
132 | 133 |
134 |
135 |
Shape
136 | 137 | 138 | 141 | 144 | 145 | 146 | 147 | 150 | 153 | 154 | 155 | 156 | 159 | 162 | 163 | 164 | 165 | 168 | 171 | 172 | 173 | 174 | 175 | 178 | 181 | 182 | 183 | 184 | 185 | 188 | 191 | 192 | 193 | 194 | 197 | 200 | 201 | 202 | 203 | 206 | 209 | 210 |
139 |

Density

140 |
142 |

143 |
148 |

Friction

149 |
151 |

152 |
157 |

Restitution

158 |
160 |

161 |
166 |

Mask Bits

167 |
169 |

170 |
176 |

Category Bits

177 |
179 |

180 |
186 |

Group Index

187 |
189 |

190 |
195 |

isSensor

196 |
198 |

199 |
204 |

Edit Shape

205 |
207 |

208 |
211 |
212 | 213 |
214 |
Body
215 | 216 | 217 | 220 | 223 | 224 | 225 | 226 | 229 | 232 | 233 | 234 | 235 | 238 | 248 | 249 | 250 | 251 | 254 | 257 | 258 | 259 | 260 | 263 | 266 | 267 | 268 | 269 | 272 | 275 | 276 | 277 | 278 | 281 | 284 | 285 | 286 | 287 | 290 | 293 | 294 | 295 | 296 | 299 | 302 | 303 | 304 | 305 | 308 | 311 | 312 | 313 | 314 | 317 | 320 | 321 | 322 | 323 | 326 | 329 | 330 | 331 | 332 | 335 | 338 | 339 | 340 | 341 | 344 | 347 | 348 | 349 | 350 | 353 | 356 | 357 |
218 |

Name

219 |
221 |

222 |
227 |

User Data

228 |
230 |

231 |
236 |

Body Type

237 |
239 |

240 | 246 |

247 |
252 |

isBullet

253 |
255 |

256 |
261 |

Rotation Fixed

262 |
264 |

265 |
270 |

Linear Damping

271 |
273 |

274 |
279 |

Angular Damping

280 |
282 |

283 |
288 |

Edit Body

289 |
291 |

292 |
297 |

Texture File

298 |
300 |

301 |
306 |

Texture Width

307 |
309 |

310 |
315 |

Texture Height

316 |
318 |

319 |
324 |

Texture Source Width

325 |
327 |

328 |
333 |

Texture Source Height

334 |
336 |

337 |
342 |

Offset X

343 |
345 |

346 |
351 |

Offet Y

352 |
354 |

355 |
358 |
359 | 360 |
361 |
Joint
362 | 363 | 364 | 367 | 370 | 371 | 372 | 373 | 376 | 379 | 380 | 381 | 382 | 385 | 390 | 391 | 392 | 393 | 396 | 399 | 400 | 401 | 402 | 403 | 406 | 409 | 410 | 411 | 412 | 415 | 418 | 419 | 420 | 421 | 422 | 425 | 428 | 429 | 430 | 431 | 434 | 437 | 438 | 439 | 440 | 443 | 446 | 447 | 448 | 449 | 450 | 453 | 456 | 457 | 458 | 459 | 462 | 467 | 468 | 469 | 470 | 473 | 478 | 479 | 480 | 481 | 484 | 487 | 488 | 489 |
365 |

Name

366 |
368 |

369 |
374 |

User Data

375 |
377 |

378 |
383 |

Joint Type

384 |
386 |

387 | type 388 |

389 |
394 |

Collide Connected

395 |
397 |

398 |
404 |

Frequency Hz

405 |
407 |

408 |
413 |

Damping Ratio

414 |
416 |

417 |
423 |

Enable Motor

424 |
426 |

427 |
432 |

Motor Speed

433 |
435 |

436 |
441 |

Max Motor Torque

442 |
444 |

445 |
451 |

Enable Limit

452 |
454 |

455 |
460 |

Lower Angle

461 |
463 |

464 | 0 465 |

466 |
471 |

Upper angle

472 |
474 |

475 | 0 476 |

477 |
482 |

Edit Joint

483 |
485 |

486 |
490 |
491 |
492 | 493 |
494 |

Create new scene

495 |
496 | Would you like to save changes to current scene? 497 |
498 |
499 | 500 | 501 | 502 |
503 |
504 | 505 | 506 |
507 |

Create shape from bitmap file

508 |
509 | Select a 24-bit grayscale bitmap image 510 | 511 |
512 |
513 | 514 | 515 |
516 |
517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /js/BMPImageLoader.js: -------------------------------------------------------------------------------- 1 | /** a 24 bit uncompressed bitmap loader */ 2 | var BMPImageLoader = (function () { 3 | 4 | function BMPImage(){ 5 | this.pixels = []; // pixel information 6 | this.width = 0; // width of the bitmap 7 | this.height = 0; // height of the bitmap 8 | } 9 | 10 | BMPImage.prototype.getPixelAt = function(x, y){ 11 | var wx = Math.min(this.width, Math.max(0, x)); 12 | var wy = Math.min(this.height, Math.max(0, y)); 13 | var r = this.pixels[3 * (this.width * wy + wx) + 0], 14 | g = this.pixels[3 * (this.width * wy + wx) + 1], 15 | b = this.pixels[3 * (this.width * wy + wx) + 2]; 16 | return [r, g, b]; 17 | }; 18 | 19 | BMPImage.prototype.dispose = function(){ 20 | delete this.pixels; 21 | }; 22 | 23 | /** 24 | * 25 | * @param bytes 26 | * string 27 | * @param offset 28 | * starting position for a 4 characters long string 29 | */ 30 | function toInt(bytes, offset) { 31 | // this might be a hack, but worked every time for me 32 | return (bytes.charCodeAt(offset + 0)) + (bytes.charCodeAt(offset + 1)) * 256 + 33 | (bytes.charCodeAt(offset + 2)) * 256 * 256 + (bytes.charCodeAt(offset + 3) * 256 * 256 * 256); 34 | } 35 | 36 | /** 37 | * 38 | * @param bytes 39 | * string 40 | * @param offset 41 | * starting position for a 2 characters long string 42 | */ 43 | function toShort(bytes, offset) { 44 | return (bytes.charCodeAt(offset + 0)) + (bytes.charCodeAt(offset + 1)); 45 | } 46 | 47 | function BMPImageLoader(){ 48 | this.counter = 0; 49 | } 50 | 51 | /** similar to ifstream but instead here counter is used to track the position of next character to be read */ 52 | BMPImageLoader.prototype.readInt = function(string){ 53 | var r = toInt(string, this.counter); 54 | this.counter += 4; 55 | return r; 56 | }; 57 | 58 | /** similar to ifstream but instead here counter is used to track the position of next character to be read */ 59 | BMPImageLoader.prototype.readShort = function(string){ 60 | var r = toShort(string, this.counter); 61 | this.counter += 2; 62 | return r; 63 | }; 64 | 65 | /** 66 | * 67 | * @param binaryFile 68 | * bitmap image as a binary string (refer FileReader API for reading a file as binary string) 69 | * returns 70 | * BMPImage 71 | */ 72 | BMPImageLoader.prototype.loadBMP = function(binaryFile){ 73 | // check if file is a bitmap 74 | var b = binaryFile.charAt(this.counter++); 75 | var m = binaryFile.charAt(this.counter++); 76 | if (b != 'B' && m != 'M'){ 77 | // not a bitmap 78 | console.log("%cIt's not a bmp", "color:#f00;"); 79 | // reset the counter for loading another files 80 | this.counter = 0; 81 | return; 82 | } 83 | 84 | // skip 8 characters 85 | for (var i = 0; i < 8; i++){ 86 | this.counter++; 87 | } 88 | 89 | var image = new BMPImage(); 90 | 91 | // position from where pixel data starts 92 | var dataOffset = this.readInt(binaryFile); 93 | 94 | // size of header 95 | var headerSize = this.readInt(binaryFile); 96 | 97 | switch (headerSize){ 98 | case 40: 99 | image.width = this.readInt(binaryFile); 100 | image.height = this.readInt(binaryFile); 101 | // console.log(image.width + " " + image.height); 102 | this.counter += 2; 103 | var format = this.readShort(binaryFile); 104 | if (format != 24){ 105 | console.log("%cImage is not 24 bit", "color:#f00;"); 106 | // reset the counter for loading another files 107 | this.counter = 0; 108 | return; 109 | } 110 | var isComprssed = this.readShort(binaryFile); 111 | if (isComprssed != 0){ 112 | console.log("%cImage is compressed", "color:#f00;"); 113 | // reset the counter for loading another files 114 | this.counter = 0; 115 | return; 116 | } 117 | break; 118 | case 12: 119 | image.width = this.readShort(line); 120 | image.height = this.readShort(line); 121 | this.counter += 2; 122 | var format = this.readShort(binaryFile); 123 | if (format != 24){ 124 | console.log("%cImage is not 24 bit", "color:#f00;"); 125 | // reset the counter for loading another files 126 | this.counter = 0; 127 | return; 128 | } 129 | break; 130 | default: 131 | // bitmap not supported 132 | console.log("%cBitmap not supported", "color:#f00;"); 133 | // reset the counter for loading another files 134 | this.counter = 0; 135 | break; 136 | } 137 | 138 | this.counter += 2; 139 | var rawBitmapData = this.readInt(binaryFile); 140 | 141 | // store pixel data in a temporary array 142 | var pixelsChar = binaryFile.slice(dataOffset, rawBitmapData + dataOffset); 143 | 144 | // getting the data in right format 145 | var width = parseInt(image.width), height = parseInt(image.height); 146 | // amount of pixel data in one row 147 | var bytesPerRow = parseInt(rawBitmapData / height);//parseInt(parseInt((width * 3.0 + 3.0) / 3.0) * 3.0) - parseInt((width * 3.0) % 3.0); 148 | 149 | var pixels = []; 150 | for(var y = 0; y < height; y++){ 151 | for(var x = 0; x < width; x++){ 152 | // bgr -> rgb 153 | // bitmap contains pixel color in [blue, green, red] format, but we need [red, green, blue] 154 | for(var c = 0; c < 3; c++){ 155 | // convert character to integer (store pixel information in [0, 255] range rather than as character) 156 | pixels[3 * (width * y + x) + c] = pixelsChar[parseInt(bytesPerRow * (height - y - 1) + 3 * x + (2 - c))].charCodeAt(0); 157 | } 158 | } 159 | } 160 | // delete the temporary array 161 | delete pixelsChar; 162 | 163 | // reset the counter for loading another files 164 | this.counter = 0; 165 | 166 | // set image pixels 167 | image.pixels = pixels; 168 | 169 | return image; 170 | }; 171 | 172 | return BMPImageLoader; 173 | 174 | })(); -------------------------------------------------------------------------------- /js/GameView.js: -------------------------------------------------------------------------------- 1 | var mouseX, mouseY, mousePVec, isMouseDown, selectedBody, mouseJoint; 2 | var _navigator; 3 | 4 | function GameView(canvas, navigator) { 5 | this.canvas = canvas; 6 | this.context = canvas.getContext('2d'); 7 | _navigator = navigator; 8 | this.world; 9 | 10 | this.worldLoader = WorldLoader(); 11 | 12 | this.hasLoaded = false; 13 | this.paused = false; 14 | } 15 | 16 | /* SETUP */ 17 | GameView.prototype.setup = function(scene, fromFile){ 18 | if (this.context){ 19 | if (fromFile){ 20 | var ref = this; 21 | $.getJSON(scene, function(data){ 22 | ref.init(data); 23 | }); 24 | } 25 | else { 26 | this.init(scene); 27 | } 28 | } 29 | }; 30 | 31 | /* RESCALE */ 32 | GameView.prototype.rescale = function(){ 33 | // canvas.setAttribute('width', window.innerWidth); 34 | // canvas.setAttribute('height', window.innerHeight); 35 | // this.draw(); 36 | }; 37 | 38 | /* INIT */ 39 | GameView.prototype.init = function(scene){ 40 | // add event listeners 41 | this.canvas.addEventListener("mousedown", function(e) { 42 | isMouseDown = true; 43 | handleMouseMove(e); 44 | this.addEventListener("mousemove", handleMouseMove, true); 45 | }, true); 46 | 47 | this.canvas.addEventListener("mouseup", function() { 48 | this.removeEventListener("mousemove", handleMouseMove, true); 49 | isMouseDown = false; 50 | mouseX = undefined; 51 | mouseY = undefined; 52 | }, true); 53 | 54 | // create physics world 55 | this.world = new b2World(new b2Vec2(0, 10), true); 56 | 57 | // set debug draw 58 | var debugDraw = new b2DebugDraw(); 59 | debugDraw.SetSprite(this.context); 60 | debugDraw.SetDrawScale(30.0); 61 | debugDraw.SetFillAlpha(0.5); 62 | debugDraw.SetLineThickness(1.0); 63 | debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); 64 | this.world.SetDebugDraw(debugDraw); 65 | 66 | // load scene 67 | this.worldLoader.loadJsonScene(scene, this.world); 68 | 69 | this.hasLoaded = true; 70 | }; 71 | 72 | /* DRAW */ 73 | GameView.prototype.draw = function(){ 74 | this.world.DrawDebugData(); 75 | this.world.ClearForces(); 76 | 77 | for (var i = 0; i < this.worldLoader.gameObjects.length; i++){ 78 | // handle sprite rotation and translation 79 | var gameObject = this.worldLoader.gameObjects[i]; 80 | this.context.save(); 81 | this.context.translate(gameObject.body.GetPosition().x * 30, gameObject.body.GetPosition().y * 30); 82 | this.context.rotate(gameObject.body.GetAngle()); 83 | 84 | if (gameObject.spriteData.length > 0){ 85 | var spriteData = gameObject.spriteData, 86 | sourceX = spriteData[0], 87 | sourceY = spriteData[1], 88 | sourceW = spriteData[2], 89 | sourceH = spriteData[3], 90 | imageW = spriteData[4], 91 | imageH = spriteData[5]; 92 | this.context.drawImage(gameObject.sprite, sourceX, sourceY, sourceW, sourceH, -imageW / 2, -imageH / 2, imageW, imageH); 93 | 94 | } 95 | else { 96 | var imageW = gameObject.sprite.width, imageH = gameObject.sprite.height; 97 | this.context.drawImage(gameObject.sprite, -imageW / 2, -imageH / 2, imageW, imageH); 98 | } 99 | this.context.restore(); 100 | } 101 | }; 102 | 103 | /* UPDATE */ 104 | GameView.prototype.update = function(){ 105 | if(isMouseDown && (!mouseJoint)) { 106 | var body = getBodyAtMouse(this.world); 107 | if(body) { 108 | var md = new b2MouseJointDef(); 109 | md.bodyA = this.world.GetGroundBody(); 110 | md.bodyB = body; 111 | md.target.Set(mouseX, mouseY); 112 | md.collideConnected = true; 113 | md.maxForce = 300.0 * body.GetMass(); 114 | mouseJoint = this.world.CreateJoint(md); 115 | body.SetAwake(true); 116 | } 117 | } 118 | 119 | if(mouseJoint) { 120 | if(isMouseDown) { 121 | mouseJoint.SetTarget(new b2Vec2(mouseX, mouseY)); 122 | } else { 123 | this.world.DestroyJoint(mouseJoint); 124 | mouseJoint = null; 125 | } 126 | } 127 | 128 | this.world.Step(1 / 60, 10, 10); 129 | }; 130 | 131 | GameView.prototype.dispose = function(){ 132 | 133 | }; 134 | 135 | GameView.prototype.updateView = function(){ 136 | if (!this.paused){ 137 | this.update(); 138 | } 139 | this.draw(); 140 | } 141 | 142 | function handleMouseMove(e) { 143 | mouseX = _navigator.screenPointToWorld(e.offsetX, e.offsetY)[0] / 30; 144 | mouseY = _navigator.screenPointToWorld(e.offsetX, e.offsetY)[1] / 30; 145 | } 146 | 147 | function getBodyAtMouse(world) { 148 | mousePVec = new b2Vec2(mouseX, mouseY); 149 | var aabb = new b2AABB(); 150 | aabb.lowerBound.Set(mouseX - 0.001, mouseY - 0.001); 151 | aabb.upperBound.Set(mouseX + 0.001, mouseY + 0.001); 152 | 153 | // Query the world for overlapping shapes. 154 | selectedBody = null; 155 | world.QueryAABB(getBodyCB, aabb); 156 | return selectedBody; 157 | } 158 | 159 | function getBodyCB(fixture) { 160 | if(fixture.GetBody().GetType() != b2Body.b2_staticBody) { 161 | if(fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), mousePVec)) { 162 | selectedBody = fixture.GetBody(); 163 | return false; 164 | } 165 | } 166 | return true; 167 | } -------------------------------------------------------------------------------- /js/PhysicsEditor.js: -------------------------------------------------------------------------------- 1 | function PhysicsEditor(canvas) { 2 | this.sceneManager = SceneManager.getInstance(); 3 | this.viewport = Viewport.getInstance(canvas, this.sceneManager); 4 | this.uiManager = UIManager.getInstance(this.sceneManager); 5 | this.gameView = null; 6 | 7 | // directory containing textures 8 | this.resourceDirectory = "resources/"; 9 | 10 | this.uiManager.initialize(this.viewport.getInputHandler()); 11 | this.uiManager.playBackControls = $("#gameplayControls").find("button"); 12 | 13 | // auto trace image shape generation paramters 14 | this.autoTrace = { 15 | xSpace : 1.0, 16 | ySpace : 1.0, 17 | concavity : 20 18 | }; 19 | 20 | // play back controls // 21 | var ref = this; 22 | this.uiManager.playBackControls[0].addEventListener("click", function(){ 23 | if (ref.gameView){ 24 | ref.gameView = null; 25 | ref.viewport.getInputHandler().inGameMode = 0; 26 | $(this).removeClass("glyphicon-stop").addClass("glyphicon-play"); 27 | } 28 | else { 29 | ref.gameView = new GameView(canvas, ref.viewport.getNavigator()); 30 | ref.gameView.setup(ref.sceneManager.exportWorld()); 31 | ref.viewport.getInputHandler().inGameMode = 1; 32 | $(this).removeClass("glyphicon-play").addClass("glyphicon-stop"); 33 | } 34 | }); 35 | this.uiManager.playBackControls[1].addEventListener("click", function(){ 36 | if (ref.gameView != null) 37 | ref.gameView.paused = !ref.gameView.paused; 38 | }); 39 | this.uiManager.playBackControls[2].addEventListener("click", function(){ 40 | if (ref.gameView != null && ref.gameView.paused) 41 | ref.gameView.update(); 42 | }); 43 | ////////////////////// 44 | 45 | // view controls // 46 | this.uiManager.viewControls = $("#viewControls").find("button"); 47 | this.uiManager.viewControls.each(function(index){ 48 | var action = $(this).data("event"); 49 | this[action] = ref.viewport[action].bind(ref.viewport); 50 | 51 | this.addEventListener("click", function(e){ 52 | e.preventDefault(); 53 | e.target[action](); 54 | }); 55 | }); 56 | /////////////////// 57 | 58 | // add event listeners to canvas 59 | var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel"; 60 | canvas.addEventListener(mousewheelevt, function(e){ 61 | ref.viewport.onMouseWheel(e); 62 | }); 63 | canvas.addEventListener("mousedown", function(e){ 64 | e.preventDefault(); 65 | ref.viewport.onMouseDown(e); 66 | ref.uiManager.onMouseDown(ref.viewport.getInputHandler()); 67 | }); 68 | canvas.addEventListener("mousemove", function(e){ 69 | e.preventDefault(); 70 | ref.viewport.onMouseMove(e); 71 | ref.uiManager.onMouseMove(ref.viewport.getInputHandler()); 72 | }); 73 | canvas.addEventListener("mouseup", function(e){ 74 | ref.viewport.onMouseUp(e); 75 | ref.uiManager.onMouseUp(ref.viewport.getInputHandler()); 76 | }); 77 | }; 78 | 79 | PhysicsEditor.prototype.cloneBody = function(body){ 80 | var clone = body.clone(); 81 | this.sceneManager.addBody(clone); 82 | return clone; 83 | }; 84 | 85 | PhysicsEditor.prototype.cloneJoint = function(joint, cloneBodyA, cloneBodyB){ 86 | var clone = joint.clone(cloneBodyA, cloneBodyB); 87 | if (cloneBodyA){ 88 | this.sceneManager.addBody(clone.bodyA); 89 | } 90 | if (cloneBodyB){ 91 | this.sceneManager.addBody(clone.bodyB); 92 | } 93 | this.sceneManager.addJoint(clone); 94 | return clone; 95 | }; 96 | 97 | PhysicsEditor.prototype.getSelectedBodies = function(){ 98 | return this.sceneManager.selectedBodies; 99 | }; 100 | 101 | PhysicsEditor.prototype.getSelectedJoints = function(){ 102 | return this.sceneManager.selectedJoints; 103 | }; 104 | 105 | PhysicsEditor.prototype.getSelectedShapes = function(){ 106 | return this.sceneManager.selectedShapes; 107 | }; 108 | 109 | PhysicsEditor.prototype.getSelectedVertices = function(){ 110 | return this.sceneManager.selectedVertices; 111 | }; 112 | 113 | PhysicsEditor.prototype.getCurrentSelection = function(){ 114 | return this.viewport.inputHandler.selection; 115 | }; 116 | 117 | PhysicsEditor.prototype.getSceneManager = function(){ 118 | return this.sceneManager; 119 | }; 120 | 121 | PhysicsEditor.prototype.getViewport = function(){ 122 | return this.viewport; 123 | }; 124 | 125 | PhysicsEditor.prototype.getUIManager = function(){ 126 | return this.uiManager; 127 | }; 128 | 129 | PhysicsEditor.prototype.getGameView = function(){ 130 | return this.gameView; 131 | }; -------------------------------------------------------------------------------- /js/PolygonHelper.js: -------------------------------------------------------------------------------- 1 | function Point(x, y){ 2 | this.x = x || 0; 3 | this.y = y || 0; 4 | } 5 | 6 | // area of triangle abc 7 | function area(a, b, c) { 8 | return (((b.x - a.x)*(c.y - a.y))-((c.x - a.x)*(b.y - a.y))); 9 | } 10 | 11 | // point b is left to line ac 12 | function left(a, b, c) { 13 | return area(a, b, c) > 0; 14 | } 15 | 16 | // point b is left or on the line ac 17 | function leftOn(a, b, c) { 18 | return area(a, b, c) >= 0; 19 | } 20 | 21 | // point b is right to the line ac 22 | function right(a, b, c) { 23 | return area(a, b, c) < 0; 24 | } 25 | 26 | // point b is right or on the line ac 27 | function rightOn(a, b, c) { 28 | return area(a, b, c) <= 0; 29 | } 30 | 31 | // point b is on the line ac 32 | function collinear(a, b, c) { 33 | return area(a, b, c) == 0; 34 | } 35 | 36 | // squared distance between point a-b 37 | function sqdist(a, b) { 38 | var dx = b.x - a.x; 39 | var dy = b.y - a.y; 40 | return dx * dx + dy * dy; 41 | } 42 | 43 | function Line(p1, p2){ 44 | this.first = p1; 45 | this.second = p2; 46 | } 47 | 48 | function eq(a, b) { 49 | return Math.abs(a - b) <= 1e-8; 50 | } 51 | 52 | // return intersecion of two line 53 | function lineInt(l1, l2) { 54 | var i = new Point(); 55 | var a1, b1, c1, a2, b2, c2, det; 56 | a1 = l1.second.y - l1.first.y; 57 | b1 = l1.first.x - l1.second.x; 58 | c1 = a1 * l1.first.x + b1 * l1.first.y; 59 | a2 = l2.second.y - l2.first.y; 60 | b2 = l2.first.x - l2.second.x; 61 | c2 = a2 * l2.first.x + b2 * l2.first.y; 62 | det = a1 * b2 - a2 * b1; 63 | if (!eq(det, 0)) { // lines are not parallel 64 | i.x = (b2 * c1 - b1 * c2) / det; 65 | i.y = (a1 * c2 - a2 * c1) / det; 66 | } 67 | return i; 68 | }; 69 | 70 | 71 | function Polygon(){ 72 | this.vertices = []; 73 | } 74 | 75 | Polygon.prototype.draw = function(context){ 76 | context.strokeStyle = "#fff"; 77 | context.moveTo(this.at(0).x, this.at(0).y); 78 | for (var i = 0; i < this.size(); i++){ 79 | context.lineTo(this.at(i).x, this.at(i).y); 80 | } 81 | context.closePath(); 82 | context.stroke(); 83 | }; 84 | 85 | // vertex at index - i 86 | Polygon.prototype.at = function(i) { 87 | var s = this.size(); 88 | return this.vertices[i < 0 ? i % s + s : i % s]; // if i < 0 return last vertex, and if i > size return first vertex 89 | }; 90 | 91 | // first vertex 92 | Polygon.prototype.first = function() { 93 | return this.vertices[0]; 94 | }; 95 | 96 | // last vertex 97 | Polygon.prototype.last = function() { 98 | return this.vertices[this.vertices.length - 1]; 99 | }; 100 | 101 | // number of vertices 102 | Polygon.prototype.size = function() { 103 | return this.vertices.length; 104 | }; 105 | 106 | // add vertex 107 | Polygon.prototype.push = function(p) { 108 | this.vertices.push(p); 109 | }; 110 | 111 | // add vertex 112 | Polygon.prototype.addPoint = function(x, y){ 113 | this.push(new Point(x, y)); 114 | }; 115 | 116 | // reverse order of vertices 117 | Polygon.prototype.reverse = function() { 118 | this.vertices.reverse(); 119 | }; 120 | 121 | // returs copy of polygon 122 | Polygon.prototype.copy = function(i, j) { 123 | var p = new Polygon(); 124 | if (i < j) { 125 | p.vertices = this.vertices.slice(i, j + 1); 126 | } else { 127 | p.vertices = this.vertices.slice(i, this.vertices.length); 128 | var tmp = this.vertices.slice(0, j + 1); 129 | for (var k = 0; k < tmp.length; k++){ 130 | p.push(tmp[k]); 131 | } 132 | delete(tmp); 133 | } 134 | return p; 135 | }; 136 | 137 | // makes polygon clockwise 138 | Polygon.prototype.makeCCW = function() { 139 | var br = 0; 140 | 141 | // find bottom right point 142 | for (var i = 1; i < this.size(); ++i) { 143 | if (this.vertices[i].y < this.vertices[br].y || (this.vertices[i].y == this.vertices[br].y && this.vertices[i].x > this.vertices[br].x)) { 144 | br = i; 145 | } 146 | } 147 | 148 | // reverse poly if clockwise 149 | if (!left(this.at(br - 1), this.at(br), this.at(br + 1))) { 150 | this.reverse(); 151 | } 152 | }; 153 | 154 | // returns whether vertex at index if reflex 155 | Polygon.prototype.isReflex = function(i) { 156 | return right(this.at(i - 1), this.at(i), this.at(i + 1)); 157 | }; 158 | 159 | // decompose polygon (if concave) to array of convex polygons 160 | Polygon.prototype.decompose = function(){ 161 | var polygons = []; // array to store convex polygons 162 | decomposePolygon(this, polygons); // decompose polygon and store them in polygons[] 163 | return polygons; // return array of polygons 164 | }; 165 | 166 | // deompose polygon 167 | function decomposePolygon(poly, polygons){ 168 | var upperInt = new Point, lowerInt = new Point, p = new Point, closestVert = new Point; 169 | var upperDist, lowerDist, d, closestDist; 170 | var upperIndex = 0, lowerIndex = 0, closestIndex = 0; 171 | var lowerPoly = new Polygon, upperPoly = new Polygon; 172 | 173 | if (poly.size() < 2){ 174 | return; 175 | } 176 | for (var i = 0; i < poly.size(); ++i) { 177 | if (poly.isReflex(i)) { 178 | upperDist = lowerDist = Number.POSITIVE_INFINITY; 179 | for (var j = 0; j < poly.size(); ++j) { 180 | if (left(poly.at(i - 1), poly.at(i), poly.at(j)) 181 | && rightOn(poly.at(i - 1), poly.at(i), poly.at(j - 1))) { // if line intersects with an edge 182 | p = lineInt(new Line(poly.at(i - 1), poly.at(i)), new Line(poly.at(j), poly.at(j - 1))); // intersection(poly.at(i - 1), poly.at(i), poly.at(j), poly.at(j - 1)); // find the point of intersection 183 | if (right(poly.at(i + 1), poly.at(i), p)) { // make sure it's inside the poly 184 | d = sqdist(poly.at(i), p); 185 | if (d < lowerDist) { // keep only the closest intersection 186 | lowerDist = d; 187 | lowerInt = p; 188 | lowerIndex = j; 189 | } 190 | } 191 | } 192 | if (left(poly.at(i + 1), poly.at(i), poly.at(j + 1)) 193 | && rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { 194 | p = lineInt(new Line(poly.at(i + 1), poly.at(i)), new Line(poly.at(j), poly.at(j + 1))); // intersection(at(poly, i + 1), at(poly, i), at(poly, j), at(poly, j + 1)); 195 | if (left(poly.at(i - 1), poly.at(i), p)) { 196 | d = sqdist(poly.at(i), p); 197 | if (d < upperDist) { 198 | upperDist = d; 199 | upperInt = p; 200 | upperIndex = j; 201 | } 202 | } 203 | } 204 | } 205 | 206 | // if there are no vertices to connect to, choose a point in the middle 207 | if (lowerIndex == (upperIndex + 1) % poly.size()) { 208 | // console.log("Case 1: Vertex(" + i + "), lowerIndex(" + lowerIndex + "), upperIndex(" + upperIndex + "), poly.size(" + poly.size()); 209 | p.x = (lowerInt.x + upperInt.x) / 2; 210 | p.y = (lowerInt.y + upperInt.y) / 2; 211 | var tmp; 212 | if (i < upperIndex) { 213 | tmp = poly.vertices.slice(i, upperIndex + 1); 214 | for (var k = 0; k < tmp.length; k++){ 215 | lowerPoly.push(tmp[k]); 216 | } 217 | lowerPoly.push(p); 218 | upperPoly.push(p); 219 | if (lowerIndex != 0) { 220 | tmp = poly.vertices.slice(lowerIndex, poly.size()); 221 | for (var k = 0; k < tmp.length; k++){ 222 | upperPoly.push(tmp[k]); 223 | } 224 | } 225 | tmp = poly.vertices.slice(0, i + 1); 226 | for (var k = 0; k < tmp.length; k++){ 227 | upperPoly.push(tmp[k]); 228 | } 229 | } else { 230 | if (i != 0) { 231 | tmp = poly.vertices.slice(i, poly.size()); 232 | for (var k = 0; k < tmp.length; k++){ 233 | lowerPoly.push(tmp[k]); 234 | } 235 | } 236 | tmp = poly.vertices.slice(0, upperIndex + 1); 237 | for (var k = 0; k < tmp.length; k++){ 238 | lowerPoly.push(tmp[k]); 239 | } 240 | lowerPoly.push(p); 241 | upperPoly.push(p); 242 | tmp = poly.vertices.slice(lowerIndex, i + 1); 243 | for (var k = 0; k < tmp.length; k++){ 244 | upperPoly.push(tmp[k]); 245 | } 246 | } 247 | } else { 248 | // connect to the closest point within the triangle 249 | // console.log("Case 2: Vertex(" + i + "), closestIndex(" + closestIndex + "), poly.size(" + poly.size()); 250 | if (lowerIndex > upperIndex) { 251 | upperIndex += poly.size(); 252 | } 253 | closestDist = Number.POSITIVE_INFINITY; 254 | for (var j = lowerIndex; j <= upperIndex; ++j) { 255 | if (leftOn(poly.at(i - 1), poly.at(i), poly.at(j)) 256 | && rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) { 257 | d = sqdist(poly.at(i), poly.at(j)); 258 | if (d < closestDist) { 259 | closestDist = d; 260 | closestVert = poly.at(j); 261 | closestIndex = j % poly.size(); 262 | } 263 | } 264 | } 265 | var tmp; 266 | if (i < closestIndex) { 267 | tmp = poly.vertices.slice(i, closestIndex + 1); 268 | for (var k = 0; k < tmp.length; k++){ 269 | lowerPoly.push(tmp[k]); 270 | } 271 | if (closestIndex != 0) { 272 | tmp = poly.vertices.slice(closestIndex, poly.size()); 273 | for (var k = 0; k < tmp.length; k++){ 274 | upperPoly.push(tmp[k]); 275 | } 276 | } 277 | tmp = poly.vertices.slice(0, i + 1); 278 | for (var k = 0; k < tmp.length; k++){ 279 | upperPoly.push(tmp[k]); 280 | } 281 | } else { 282 | if (i != 0) { 283 | tmp = poly.vertices.slice(i, poly.size()); 284 | for (var k = 0; k < tmp.length; k++){ 285 | lowerPoly.push(tmp[k]); 286 | } 287 | } 288 | tmp = poly.vertices.slice(0, closestIndex + 1); 289 | for (var k = 0; k < tmp.length; k++){ 290 | lowerPoly.push(tmp[k]); 291 | } 292 | tmp = poly.vertices.slice(closestIndex, i + 1); 293 | for (var k = 0; k < tmp.length; k++){ 294 | upperPoly.push(tmp[k]); 295 | } 296 | } 297 | } 298 | 299 | // solve smallest poly first 300 | if (lowerPoly.size() < upperPoly.size()) { 301 | decomposePolygon(lowerPoly, polygons); 302 | decomposePolygon(upperPoly, polygons); 303 | } else { 304 | decomposePolygon(upperPoly, polygons); 305 | decomposePolygon(lowerPoly, polygons); 306 | } 307 | return; 308 | } 309 | } 310 | polygons.push(poly); 311 | }; -------------------------------------------------------------------------------- /js/UIManager.js: -------------------------------------------------------------------------------- 1 | var UIManager = (function(){ 2 | 3 | function UIManager(sceneManager){ 4 | this.sceneManager = sceneManager; 5 | this.xyInput = []; 6 | this.taskbar = []; 7 | this.viewControls = []; 8 | this.playBackControls = []; 9 | this.shapeProperties = []; // density, friction, restitution, isSensor, edit 10 | this.bodyProperties = []; // name, userdata, type, isBullet, edit, tex_file, tex_width, tex_height 11 | this.jointProperties = []; // name, userdata, type, collideConnected, joint_specific_parameters 12 | this.jointPropertyRows = []; 13 | } 14 | 15 | UIManager.prototype.initialize = function(inputHandler){ 16 | var sceneManager = this.sceneManager; 17 | 18 | // hide separators 19 | var elementsToHide = document.getElementsByClassName("separator"); 20 | for (var i = 0; i < elementsToHide.length; i++){ 21 | elementsToHide[i].style.visibility = "hidden"; 22 | } 23 | 24 | // hide alert dialog box 25 | var alertDialog = $("#alert_dialog"); 26 | alertDialog.hide(); 27 | var alertButtons = alertDialog.find("button"); 28 | alertButtons[0].addEventListener("click", function(){ 29 | alertDialog.hide(); 30 | var data = new Blob([JSON.stringify(sceneManager.saveScene(), null, 4)], {type:'text/plain'}); 31 | var textFile = window.URL.createObjectURL(data); 32 | window.open(textFile); 33 | sceneManager.newScene(); 34 | }); 35 | alertButtons[1].addEventListener("click", function(){ 36 | alertDialog.hide(); 37 | sceneManager.newScene(); 38 | }); 39 | alertButtons[2].addEventListener("click", function(){ 40 | alertDialog.hide(); 41 | }); 42 | 43 | // hide auto shape option view 44 | var autoShape = $("#auto_shape"); 45 | autoShape.hide(); 46 | var shapeButtons = autoShape.find("button"); 47 | shapeButtons[0].addEventListener("click", function(){ 48 | $('#loadBitmap')[0].value = null; 49 | $("#loadBitmap").trigger('click'); 50 | autoShape.hide(); 51 | }); 52 | shapeButtons[1].addEventListener("click", function(){ 53 | autoShape.hide(); 54 | }); 55 | $('#loadBitmap').change(function(e){ 56 | if (e.target.files.length < 0){ 57 | return; 58 | } 59 | if(e.target.files[0].name && e.target.files[0].type == "image/bmp"){ 60 | var reader = new FileReader(); 61 | reader.readAsBinaryString(e.target.files[0]); 62 | reader.onload = function(e){ 63 | var loader = new BMPImageLoader(); 64 | var image = loader.loadBMP(e.target.result); 65 | 66 | // default settings 67 | var xSpace = Editor.autoTrace.xSpace, 68 | ySpace = Editor.autoTrace.ySpace, 69 | dataWidth = parseInt(image.width / xSpace), 70 | dataHeight = parseInt(image.height / ySpace), 71 | alphaThreshold = 127, 72 | concavity = Editor.autoTrace.concavity; 73 | var points = []; 74 | for (var i = 0; i < dataHeight; i++){ 75 | for (var j = 0; j < dataWidth; j++){ 76 | var pixel = image.getPixelAt(j * xSpace, i * ySpace); 77 | if ((pixel[0]) >= alphaThreshold){ 78 | points.push([j * xSpace - image.width / 2 , i * ySpace - image.height / 2]); 79 | } 80 | } 81 | } 82 | // create concave hull from points 83 | var concaveHull = hull(points, concavity); 84 | delete points; 85 | 86 | // create shape 87 | sceneManager.createShapeFromPoints(concaveHull); 88 | 89 | delete concaveHull; 90 | 91 | // release image 92 | image.dispose(); 93 | delete image; 94 | } 95 | } 96 | }); 97 | 98 | // initialize transform tool buttons 99 | $("#transformTools").find("button").each(function(index){ 100 | var action = $(this).data("event"); 101 | mixin(this, inputHandler, action); 102 | 103 | this.addEventListener("click", function(e){ 104 | e.preventDefault(); 105 | e.target[action](); 106 | }); 107 | }); 108 | 109 | // initialize pivot tool buttons 110 | $("#pivotTools").find("button").each(function(index){ 111 | var action = $(this).data("event"); 112 | mixin(this, inputHandler, action); 113 | 114 | this.addEventListener("click", function(e){ 115 | e.preventDefault(); 116 | e.target[action](); 117 | }); 118 | }); 119 | 120 | this.xyInput = [$("#input_x")[0], $("#input_y")[0]]; 121 | 122 | this.xyInput[0].addEventListener("keypress", function(e){ 123 | if (e.which == 13){ // enter pressed 124 | if (!isNaN(parseFloat(this.value))){ 125 | if (inputHandler.transformTool == 5){ 126 | sceneManager.setScaleOfSelectedObjects(parseFloat(this.value), null, 0, inputHandler); 127 | } 128 | else if (inputHandler.transformTool == 7){ 129 | sceneManager.setPositionOfSelectedObjects(parseFloat(this.value), null, 0, inputHandler); 130 | } 131 | else if (inputHandler.transformTool == 6){ 132 | sceneManager.setRotationOfSelectedObjects(parseFloat(this.value), 0, inputHandler); 133 | } 134 | } 135 | } 136 | }); 137 | 138 | this.xyInput[1].addEventListener("keypress", function(e){ 139 | if (e.which == 13){ // enter pressed 140 | if (!isNaN(parseFloat(this.value))){ 141 | if (inputHandler.transformTool == 5){ 142 | sceneManager.setScaleOfSelectedObjects(null, parseFloat(this.value), 0, inputHandler); 143 | } 144 | else if (inputHandler.transformTool == 7){ 145 | sceneManager.setPositionOfSelectedObjects(null, parseFloat(this.value), 0, inputHandler); 146 | } 147 | } 148 | } 149 | }); 150 | $("#loadScene").hide(); 151 | $("#fileMenu").find("a").each(function(index){ 152 | var action = $(this).data("event"); 153 | 154 | // mixin(this, sceneManager, action); 155 | 156 | this.addEventListener("click", function(e){ 157 | e.preventDefault(); 158 | if (action == 'newScene'){ 159 | alertDialog.show(); 160 | return; 161 | } 162 | else if (action == 'loadScene'){ 163 | $('#loadScene')[0].value = null; 164 | $("#loadScene").trigger('click'); 165 | return; 166 | } 167 | else if (action == 'saveScene'){ 168 | var data = new Blob([JSON.stringify(sceneManager.saveScene(), null, 4)], {type:'text/plain'}); 169 | var textFile = window.URL.createObjectURL(data); 170 | window.open(textFile); 171 | return; 172 | } 173 | else if (action == 'exportWorld'){ 174 | var data = new Blob([JSON.stringify(sceneManager.exportWorld(), null, 4)], {type:'text/plain'}); 175 | var textFile = window.URL.createObjectURL(data); 176 | window.open(textFile); 177 | return; 178 | } 179 | else if (action == 'exportSelection'){ 180 | var data = new Blob([JSON.stringify(sceneManager.exportSelection(), null, 4)], {type:'text/plain'}); 181 | var textFile = window.URL.createObjectURL(data); 182 | window.open(textFile); 183 | return; 184 | } 185 | }); 186 | }); 187 | $('#loadScene').change(function(e){ 188 | if (e.target.files.length < 0){ 189 | return; 190 | } 191 | if(e.target.files[0].name){ 192 | var reader = new FileReader(); 193 | reader.readAsText(e.target.files[0]); 194 | reader.onload = function(e){ 195 | sceneManager.newScene(); 196 | sceneManager.loadScene(JSON.parse(e.target.result)); 197 | } 198 | } 199 | }); 200 | $('#editMenu').find("a")[0].addEventListener("click", function(e){ 201 | sceneManager.deleteSelectedObjects(); 202 | }); 203 | $("#addToScene").find("a").each(function(index){ 204 | mixin(this, sceneManager, "createBody"); 205 | 206 | var params = parseInt($(this).data("shape")); 207 | 208 | this.addEventListener("click", function(e){ 209 | e.preventDefault(); 210 | var shapeType = parseInt(params / 10); 211 | if (shapeType == 0){ 212 | e.target["createBody"](shapeType); 213 | } 214 | else { 215 | e.target["createBody"](shapeType, params % 10); 216 | } 217 | }); 218 | }); 219 | $("#addToBody").find("a").each(function(index){ 220 | mixin(this, sceneManager, "createShape"); 221 | 222 | var params = parseInt($(this).data("shape")); 223 | 224 | this.addEventListener("click", function(e){ 225 | e.preventDefault(); 226 | var shapeType = parseInt(params / 10); 227 | if (shapeType == 0){ 228 | e.target["createShape"](shapeType); 229 | } 230 | else { 231 | if (params % 10 != 2){ 232 | e.target["createShape"](shapeType, params % 10); 233 | } 234 | else { 235 | autoShape.show(); 236 | } 237 | } 238 | }); 239 | }); 240 | $("#createJoint").find("a").each(function(index){ 241 | mixin(this, sceneManager, "createJoint"); 242 | 243 | var type = parseInt($(this).data("type")); 244 | 245 | this.addEventListener("click", function(e){ 246 | e.preventDefault(); 247 | e.target["createJoint"](type); 248 | }); 249 | }); 250 | 251 | // properties of selected shape(s) 252 | this.shapeProperties = $("#shape_properties").find("input"); 253 | for (var i = 0; i < 6; i++){ 254 | this.shapeProperties[i].addEventListener('keypress', function(e){ 255 | if (e.which == 13){ 256 | var property = $(this).data('property'); 257 | for (var i = 0; i < sceneManager.selectedShapes.length; i++){ 258 | if (parseFloat(this.value) != NaN) 259 | sceneManager.selectedShapes[i][property] = parseFloat(this.value); 260 | } 261 | } 262 | }); 263 | } 264 | $(this.shapeProperties[6]).change(function(){ 265 | for (var i = 0; i < sceneManager.selectedShapes.length; i++){ 266 | var property = $(this).data('property'); 267 | sceneManager.selectedShapes[i][property] = $(this).is(":checked"); 268 | } 269 | }); 270 | var ref = this; 271 | this.shapeProperties[7].addEventListener('click', function(){ 272 | if (sceneManager.state == sceneManager.STATE_BODY_EDIT_MODE){ 273 | sceneManager.enterShapeEditMode(); 274 | this.value = "Done"; 275 | ref.updateShapePropertyView(); 276 | ref.updateBodyPropertyView(); 277 | } 278 | else if (sceneManager.state == sceneManager.STATE_SHAPE_EDIT_MODE){ 279 | sceneManager.enterBodyEditMode(); 280 | this.value = "Edit"; 281 | ref.updateShapePropertyView(); 282 | ref.updateBodyPropertyView(); 283 | } 284 | }); 285 | 286 | // properties of selected body(s) 287 | this.bodyProperties = $("#body_properties").find("input"); 288 | this.bodyProperties.push($("#body_properties").find("select")[0]); 289 | for (var i = 0; i < 2; i++){ 290 | this.bodyProperties[i].addEventListener('keypress', function(e){ 291 | if (e.which == 13){ 292 | var property = $(this).data('property'); 293 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 294 | sceneManager.selectedBodies[i][property] = this.value; 295 | } 296 | } 297 | }); 298 | } 299 | for (var i = 4; i < 6; i++){ 300 | this.bodyProperties[i].addEventListener('keypress', function(e){ 301 | if (e.which == 13){ 302 | var property = $(this).data('property'); 303 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 304 | if (parseFloat(this.value) != NaN) 305 | sceneManager.selectedBodies[i][property] = parseFloat(this.value); 306 | } 307 | } 308 | }); 309 | } 310 | $(this.bodyProperties[2]).change(function(){ 311 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 312 | var property = $(this).data('property'); 313 | sceneManager.selectedBodies[i][property] = $(this).is(":checked"); 314 | } 315 | }); 316 | $(this.bodyProperties[3]).change(function(){ 317 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 318 | var property = $(this).data('property'); 319 | sceneManager.selectedBodies[i][property] = $(this).is(":checked"); 320 | } 321 | }); 322 | this.bodyProperties[6].addEventListener('click', function(){ 323 | if (sceneManager.state == sceneManager.STATE_DEFAULT_MODE){ 324 | sceneManager.enterBodyEditMode(); 325 | this.value = "Done"; 326 | ref.updateShapePropertyView(); 327 | ref.updateBodyPropertyView(); 328 | ref.updateJointPropertyView(); 329 | } 330 | else if (sceneManager.state == sceneManager.STATE_BODY_EDIT_MODE) { 331 | sceneManager.enterDefaultMode(); 332 | this.value = "Edit"; 333 | ref.updateShapePropertyView(); 334 | ref.updateBodyPropertyView(); 335 | ref.updateJointPropertyView(); 336 | } 337 | }); 338 | $(this.bodyProperties[14]).change(function(){ 339 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 340 | var property = $(this).data('property'); 341 | sceneManager.selectedBodies[i][property] = parseInt(this.value); 342 | } 343 | }); 344 | this.bodyProperties[7].addEventListener("click", function(e){ 345 | this.value = null; 346 | }); 347 | $(this.bodyProperties[7]).change(function(e){ 348 | // console.log(e.target.files[0].type); 349 | if (e.target.files == null && e.target.files.length < 0){ 350 | return; 351 | } 352 | if(e.target.files[0].name && (e.target.files[0].type == "image/png" || e.target.files[0].type == "image/jpeg" || e.target.files[0].type == "image/bmp")){ 353 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 354 | sceneManager.selectedBodies[i].setSprite(Editor.resourceDirectory + e.target.files[0].name); 355 | } 356 | } 357 | }); 358 | for (var i = 8; i < 14; i++){ 359 | this.bodyProperties[i].addEventListener('keypress', function(e){ 360 | if (e.which == 13){ 361 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 362 | if (parseFloat(this.value) != NaN){ 363 | var action = $(this).data('action'); 364 | sceneManager.selectedBodies[i][action](parseFloat(this.value)); 365 | } 366 | } 367 | } 368 | }); 369 | } 370 | 371 | // properties of selected joint(s) 372 | this.jointProperties = $("#joint_properties").find("input"); 373 | this.jointPropertyRows = $("#joint_properties").find("tr"); 374 | for (var i = 0; i < 8; i++){ 375 | if (i == 2 || i == 5){ 376 | continue; 377 | } 378 | this.jointProperties[i].addEventListener('keypress', function(e){ 379 | if (e.which == 13){ 380 | for (var j = 0; j < sceneManager.selectedJoints.length; j++){ 381 | var property = $(this).data('property'); 382 | if (i < 2){ 383 | sceneManager.selectedJoints[j][property] = this.value; 384 | } 385 | else { 386 | if (parseFloat(this.value) != null){ 387 | sceneManager.selectedJoints[j][property] = parseFloat(this.value); 388 | } 389 | } 390 | } 391 | } 392 | }); 393 | } 394 | $(this.jointProperties[2]).change(function(){ 395 | var property = $(this).data('property'); 396 | for (var i = 0; i < sceneManager.selectedJoints.length; i++){ 397 | sceneManager.selectedJoints[i][property] = $(this).is(":checked"); 398 | } 399 | }); 400 | $(this.jointProperties[5]).change(function(){ 401 | var property = $(this).data('property'); 402 | for (var i = 0; i < sceneManager.selectedJoints.length; i++){ 403 | sceneManager.selectedJoints[i][property] = $(this).is(":checked"); 404 | } 405 | }); 406 | $(this.jointProperties[8]).change(function(){ 407 | var property = $(this).data('property'); 408 | for (var i = 0; i < sceneManager.selectedJoints.length; i++){ 409 | sceneManager.selectedJoints[i][property] = $(this).is(":checked"); 410 | } 411 | }); 412 | this.jointProperties[9].addEventListener('click', function(){ 413 | if (sceneManager.selectedJoints[0].inEditMode){ 414 | sceneManager.selectedJoints[0].inEditMode = false; 415 | this.value = "Edit"; 416 | } 417 | else { 418 | sceneManager.selectedJoints[0].inEditMode = true; 419 | this.value = "Done" 420 | } 421 | }); 422 | 423 | this.updateShapePropertyView(); 424 | this.updateBodyPropertyView(); 425 | this.updateJointPropertyView(); 426 | }; 427 | 428 | UIManager.prototype.onMouseDown = function(inputHandler){ 429 | this.updateShapePropertyView(); 430 | this.updateBodyPropertyView(); 431 | this.updateJointPropertyView(); 432 | 433 | // update input-xy 434 | if (inputHandler.selection.length != 1){ 435 | this.xyInput[0].value = ""; 436 | this.xyInput[1].value = ""; 437 | return; 438 | } 439 | this.updateInputXY(inputHandler); 440 | }; 441 | 442 | UIManager.prototype.onMouseMove = function(inputHandler){ 443 | this.updateShapePropertyView(); 444 | this.updateBodyPropertyView(); 445 | this.updateJointPropertyView(); 446 | 447 | // update input-xy 448 | if (inputHandler.selection.length != 1){ 449 | this.xyInput[0].value = ""; 450 | this.xyInput[1].value = ""; 451 | return; 452 | } 453 | this.updateInputXY(inputHandler); 454 | }; 455 | 456 | UIManager.prototype.onMouseUp = function(inputHandler){ 457 | this.updateShapePropertyView(); 458 | this.updateBodyPropertyView(); 459 | this.updateJointPropertyView(); 460 | 461 | if (inputHandler.selection.length != 1){ 462 | this.xyInput[0].value = ""; 463 | this.xyInput[1].value = ""; 464 | return; 465 | } 466 | this.updateInputXY(inputHandler); 467 | }; 468 | 469 | UIManager.prototype.updateShapePropertyView = function(){ 470 | var sceneManager = this.sceneManager; 471 | if ((sceneManager.state == sceneManager.STATE_BODY_EDIT_MODE || 472 | sceneManager.state == sceneManager.STATE_SHAPE_EDIT_MODE) && 473 | sceneManager.selectedShapes.length > 0){ 474 | $("#shape_properties").show(); 475 | 476 | if(sceneManager.selectedShapes.length == 1){ 477 | this.shapeProperties[0].disabled = false; 478 | this.shapeProperties[0].value = sceneManager.selectedShapes[0].density; 479 | this.shapeProperties[1].value = sceneManager.selectedShapes[0].friction; 480 | this.shapeProperties[2].value = sceneManager.selectedShapes[0].restitution; 481 | this.shapeProperties[3].value = sceneManager.selectedShapes[0].maskBits; 482 | this.shapeProperties[4].value = sceneManager.selectedShapes[0].categoryBits; 483 | this.shapeProperties[5].value = sceneManager.selectedShapes[0].groupIndex; 484 | this.shapeProperties[6].checked = sceneManager.selectedShapes[0].isSensor; 485 | this.shapeProperties[7].disabled = false; 486 | } 487 | else { 488 | this.shapeProperties[0].value = ""; 489 | // this.shapeProperties[0].disabled = true; 490 | this.shapeProperties[1].value = ""; 491 | this.shapeProperties[6].value = ""; 492 | this.shapeProperties[7].disabled = true; 493 | 494 | var allAreSensor = false; 495 | for (var i = 0; i < sceneManager.selectedShapes.length; i++){ 496 | if (allAreSensor != sceneManager.selectedShapes[i].isSensor && i != 0){ 497 | allAreSensor = false; 498 | break; 499 | } 500 | else { 501 | allAreSensor = sceneManager.selectedShapes[i].isSensor; 502 | } 503 | } 504 | this.shapeProperties[3].checked = allAreSensor; 505 | } 506 | } 507 | else { 508 | // hide this view 509 | $("#shape_properties").hide(); 510 | } 511 | }; 512 | 513 | UIManager.prototype.updateBodyPropertyView = function(){ 514 | var sceneManager = this.sceneManager; 515 | if ((sceneManager.state == sceneManager.STATE_DEFAULT_MODE || 516 | sceneManager.state == sceneManager.STATE_BODY_EDIT_MODE) && 517 | sceneManager.selectedBodies.length > 0){ 518 | $("#body_properties").show(); 519 | 520 | if(sceneManager.selectedBodies.length == 1){ 521 | var cachedBody = sceneManager.selectedBodies[0]; 522 | this.bodyProperties[0].disabled = false; 523 | this.bodyProperties[0].value = cachedBody.name; 524 | this.bodyProperties[1].value = cachedBody.userData; 525 | this.bodyProperties[2].checked = cachedBody.isBullet; 526 | this.bodyProperties[3].checked = cachedBody.isFixedRotation; 527 | this.bodyProperties[4].value = cachedBody.linearDamping; 528 | this.bodyProperties[5].value = cachedBody.angularDamping; 529 | this.bodyProperties[6].disabled = false; 530 | this.bodyProperties[14].value = cachedBody.bodyType; 531 | 532 | if (cachedBody.sprite != null){ 533 | this.bodyProperties[8].value = cachedBody.getSpriteWidth(); 534 | this.bodyProperties[9].value = cachedBody.getSpriteHeight(); 535 | this.bodyProperties[10].value = cachedBody.getSpriteSourceWidth() != null ? cachedBody.getSpriteSourceWidth() : "-"; 536 | this.bodyProperties[11].value = cachedBody.getSpriteSourceHeight() != null ? cachedBody.getSpriteSourceHeight() : "-"; 537 | this.bodyProperties[12].value = cachedBody.getSpriteOffsetX() != null ? cachedBody.getSpriteOffsetX() : "-"; 538 | this.bodyProperties[13].value = cachedBody.getSpriteOffsetY() != null ? cachedBody.getSpriteOffsetY() : "-"; 539 | } 540 | else { 541 | this.bodyProperties[8].value = ""; 542 | this.bodyProperties[9].value = ""; 543 | this.bodyProperties[10].value = ""; 544 | this.bodyProperties[11].value = ""; 545 | this.bodyProperties[12].value = ""; 546 | this.bodyProperties[13].value = ""; 547 | } 548 | 549 | } 550 | else { 551 | this.bodyProperties[0].disabled = true; 552 | this.bodyProperties[0].value = ""; 553 | this.bodyProperties[1].value = ""; 554 | this.bodyProperties[4].value = ""; 555 | this.bodyProperties[5].value = ""; 556 | this.bodyProperties[6].disabled = true; 557 | 558 | var allAreBullet = false, allHaveFixedRotation = false, allHaveSameBodyType = 0; 559 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 560 | if (allAreBullet != sceneManager.selectedBodies[i].isBullet && i != 0){ 561 | allAreBullet = false; 562 | break; 563 | } 564 | else { 565 | allAreBullet = sceneManager.selectedBodies[i].isBullet; 566 | } 567 | } 568 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 569 | if (allHaveFixedRotation != sceneManager.selectedBodies[i].isFixedRotation && i != 0){ 570 | allHaveFixedRotation = false; 571 | break; 572 | } 573 | else { 574 | allHaveFixedRotation = sceneManager.selectedBodies[i].isFixedRotation; 575 | } 576 | } 577 | for (var i = 0; i < sceneManager.selectedBodies.length; i++){ 578 | if (allHaveSameBodyType != sceneManager.selectedBodies[i].bodyType && i != 0){ 579 | allHaveSameBodyType = 3; 580 | break; 581 | } 582 | else { 583 | allHaveSameBodyType = sceneManager.selectedBodies[i].bodyType; 584 | } 585 | } 586 | this.bodyProperties[2].checked = allAreBullet; 587 | this.bodyProperties[3].checked = allHaveFixedRotation; 588 | this.bodyProperties[14].value = allHaveSameBodyType; 589 | 590 | this.bodyProperties[8].value = ""; 591 | this.bodyProperties[9].value = ""; 592 | this.bodyProperties[10].value = ""; 593 | this.bodyProperties[11].value = ""; 594 | } 595 | } 596 | else { 597 | // hide this view 598 | $("#body_properties").hide(); 599 | } 600 | }; 601 | 602 | UIManager.prototype.updateJointPropertyView = function(){ 603 | var sceneManager = this.sceneManager; 604 | if (sceneManager.state == sceneManager.STATE_DEFAULT_MODE && 605 | sceneManager.selectedJoints.length > 0){ 606 | $("#joint_properties").show(); 607 | var jointNames = ["Distance", "Weld", "Revolute", "Wheel", "Pulley", "Gear", "Prismatic", "Rope"]; 608 | if(sceneManager.selectedJoints.length == 1){ 609 | var cachedJoint = sceneManager.selectedJoints[0]; 610 | $(this.jointPropertyRows[2]).find("p")[1].innerHTML = jointNames[cachedJoint.jointType]; 611 | $(this.jointPropertyRows[this.jointPropertyRows.length - 1]).show(); 612 | 613 | this.jointProperties[0].disabled = false; 614 | this.jointProperties[0].value = cachedJoint.name; 615 | this.jointProperties[1].value = cachedJoint.userData; 616 | this.jointProperties[2].checked = cachedJoint.collideConnected; 617 | 618 | // distance or wheel joint 619 | if (cachedJoint.jointType == 0 || cachedJoint.jointType == 3){ 620 | $(this.jointPropertyRows[4]).find("p")[0].innerHTML = "Frequency Hz"; 621 | $(this.jointPropertyRows[4]).show(); 622 | $(this.jointPropertyRows[5]).show(); 623 | this.jointProperties[3].value = cachedJoint.frequencyHZ; 624 | this.jointProperties[4].value = cachedJoint.dampingRatio; 625 | } 626 | else { 627 | $(this.jointPropertyRows[4]).hide(); 628 | $(this.jointPropertyRows[5]).hide(); 629 | } 630 | 631 | // pulley, gear or rope joint 632 | if (cachedJoint.jointType == 4 || cachedJoint.jointType == 5 || cachedJoint.jointType == 7){ 633 | if (cachedJoint.jointType == 7){ 634 | $(this.jointPropertyRows[4]).find("p")[0].innerHTML = "Max Length"; 635 | } 636 | else{ 637 | $(this.jointPropertyRows[4]).find("p")[0].innerHTML = "Ratio"; 638 | } 639 | this.jointProperties[3].value = cachedJoint.frequencyHZ.toFixed(3); 640 | $(this.jointPropertyRows[4]).show(); 641 | } 642 | 643 | 644 | // revolute, wheel joint or prismatic joint 645 | if (cachedJoint.jointType == 2 || cachedJoint.jointType == 3 || cachedJoint.jointType == 6){ 646 | $(this.jointPropertyRows[6]).show(); 647 | $(this.jointPropertyRows[7]).show(); 648 | $(this.jointPropertyRows[8]).show(); 649 | if (cachedJoint.jointType == 6){ 650 | $(this.jointPropertyRows[8]).find("p")[0].innerHTML = "Max Motor Force" 651 | } 652 | else { 653 | $(this.jointPropertyRows[8]).find("p")[0].innerHTML = "Max Motor Torque" 654 | } 655 | this.jointProperties[5].checked = cachedJoint.enableMotor; 656 | this.jointProperties[6].value = cachedJoint.motorSpeed; 657 | this.jointProperties[7].value = cachedJoint.maxMotorTorque; 658 | } 659 | else { 660 | $(this.jointPropertyRows[6]).hide(); 661 | $(this.jointPropertyRows[7]).hide(); 662 | $(this.jointPropertyRows[8]).hide(); 663 | } 664 | 665 | // revolute joint 666 | if (cachedJoint.jointType == 2 || cachedJoint.jointType == 6){ 667 | this.jointProperties[8].checked = cachedJoint.enableLimit; 668 | if (cachedJoint.jointType == 2){ 669 | $(this.jointPropertyRows[10]).find("p")[0].innerHTML = "Lower Angle"; 670 | $(this.jointPropertyRows[11]).find("p")[0].innerHTML = "Upper Angle"; 671 | $(this.jointPropertyRows[10]).find("p")[1].innerHTML = cachedJoint.lowerAngle.toFixed(3); 672 | $(this.jointPropertyRows[11]).find("p")[1].innerHTML = cachedJoint.upperAngle.toFixed(3); 673 | } 674 | else { 675 | $(this.jointPropertyRows[10]).find("p")[0].innerHTML = "Lower Translation"; 676 | $(this.jointPropertyRows[11]).find("p")[0].innerHTML = "Upper Translation"; 677 | $(this.jointPropertyRows[10]).find("p")[1].innerHTML = cachedJoint.lowerTranslation.toFixed(3); 678 | $(this.jointPropertyRows[11]).find("p")[1].innerHTML = cachedJoint.upperTranslation.toFixed(3); 679 | } 680 | $(this.jointPropertyRows[9]).show(); 681 | $(this.jointPropertyRows[10]).show(); 682 | $(this.jointPropertyRows[11]).show(); 683 | } 684 | else { 685 | $(this.jointPropertyRows[9]).hide(); 686 | $(this.jointPropertyRows[10]).hide(); 687 | $(this.jointPropertyRows[11]).hide(); 688 | } 689 | } 690 | else { 691 | this.jointProperties[0].disabled = true; 692 | this.jointProperties[0].value = ""; 693 | this.jointProperties[1].value = ""; 694 | $(this.jointPropertyRows[2]).find("p")[1].innerHTML = ""; 695 | for (var i = 4; i < this.jointPropertyRows.length; i++){ 696 | $(this.jointPropertyRows[i]).hide(); 697 | } 698 | } 699 | } 700 | else { 701 | // hide this view 702 | $("#joint_properties").hide(); 703 | } 704 | }; 705 | 706 | // update input-xy whenever an object is selected / moved 707 | UIManager.prototype.updateInputXY = function(inputHandler){ 708 | if (inputHandler.transformTool == 5){ 709 | if (this.sceneManager.state != this.sceneManager.STATE_SHAPE_EDIT_MODE){ 710 | this.xyInput[0].value = inputHandler.selection[0].scaleXY[0].toFixed(2); 711 | this.xyInput[1].value = inputHandler.selection[0].scaleXY[1].toFixed(2); 712 | } 713 | else { 714 | this.xyInput[0].value = ""; 715 | this.xyInput[1].value = ""; 716 | } 717 | } 718 | else if (inputHandler.transformTool == 7){ 719 | if (this.sceneManager.state == this.sceneManager.STATE_SHAPE_EDIT_MODE){ 720 | if (inputHandler.selection.length == 1 && inputHandler.selection[0].x){ 721 | this.xyInput[0].value = inputHandler.selection[0].x.toFixed(2); 722 | this.xyInput[1].value = inputHandler.selection[0].y.toFixed(2); 723 | } 724 | } 725 | else { 726 | if (inputHandler.selection[0].position){ 727 | this.xyInput[0].value = inputHandler.selection[0].position[0].toFixed(2); 728 | this.xyInput[1].value = inputHandler.selection[0].position[1].toFixed(2); 729 | } 730 | } 731 | } 732 | else if (inputHandler.transformTool == 6){ 733 | if (this.sceneManager.state != this.sceneManager.STATE_SHAPE_EDIT_MODE && inputHandler.selection[0].rotation){ 734 | this.xyInput[0].value = inputHandler.selection[0].rotation.toFixed(2); 735 | this.xyInput[1].value = ""; 736 | } 737 | else { 738 | this.xyInput[0].value = ""; 739 | this.xyInput[1].value = ""; 740 | } 741 | } 742 | }; 743 | 744 | // binds custom object methods to dom events 745 | function mixin(target, source, methods){ 746 | for (var ii = 2, ll = arguments.length; ii < ll; ii++){ 747 | var method = arguments[ii]; 748 | target[method] = source[method].bind(source); 749 | } 750 | } 751 | 752 | var instance; 753 | return{ 754 | getInstance: function(sceneManager){ 755 | if (instance == null){ 756 | instance = new UIManager(sceneManager); 757 | instance.constructor = null; 758 | } 759 | return instance; 760 | } 761 | }; 762 | 763 | })(); -------------------------------------------------------------------------------- /js/hull.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.hull=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o Array 19 | return (_cells[x] !== undefined && _cells[x][y] !== undefined) ? _cells[x][y] : []; 20 | }; 21 | 22 | this.removePoint = function(point) { // (Array) -> Array 23 | var cellXY = this.point2CellXY(point), 24 | cell = _cells[cellXY[0]][cellXY[1]], 25 | pointIdxInCell; 26 | 27 | for (var i = 0; i < cell.length; i++) { 28 | if (cell[i][0] === point[0] && cell[i][1] === point[1]) { 29 | pointIdxInCell = i; 30 | break; 31 | } 32 | } 33 | 34 | cell.splice(pointIdxInCell, 1); 35 | 36 | return cell; 37 | }; 38 | } 39 | 40 | Grid.prototype = { 41 | point2CellXY: function(point) { // (Array) -> Array 42 | var x = parseInt(point[0] / Grid.CELL_SIZE), 43 | y = parseInt(point[1] / Grid.CELL_SIZE); 44 | return [x, y]; 45 | }, 46 | 47 | rangePoints: function(bbox) { // (Array) -> Array 48 | var tlCellXY = this.point2CellXY([bbox[0], bbox[1]]), 49 | brCellXY = this.point2CellXY([bbox[2], bbox[3]]), 50 | points = []; 51 | 52 | for (var x = tlCellXY[0]; x <= brCellXY[0]; x++) { 53 | for (var y = tlCellXY[1]; y <= brCellXY[1]; y++) { 54 | points = points.concat(this.cellPoints(x, y)); 55 | } 56 | } 57 | 58 | return points; 59 | }, 60 | 61 | addBorder2Bbox: function(bbox, border) { // (Array, Number) -> Array 62 | return [ 63 | bbox[0] - (border * Grid.CELL_SIZE), 64 | bbox[1] - (border * Grid.CELL_SIZE), 65 | bbox[2] + (border * Grid.CELL_SIZE), 66 | bbox[3] + (border * Grid.CELL_SIZE) 67 | ]; 68 | } 69 | }; 70 | 71 | function grid(points) { 72 | return new Grid(points); 73 | } 74 | 75 | Grid.CELL_SIZE = 10; 76 | 77 | module.exports = grid; 78 | },{}],2:[function(require,module,exports){ 79 | /* 80 | (c) 2014-2015, Andrii Heonia 81 | Hull.js, a JavaScript library for concave hull generation by set of points. 82 | https://github.com/AndriiHeonia/hull 83 | */ 84 | 85 | 'use strict'; 86 | 87 | var intersect = require('./intersect.js'); 88 | var grid = require('./grid.js'); 89 | 90 | function _formatToXy(pointset, format) { 91 | if (format === undefined) { 92 | return pointset; 93 | } 94 | return pointset.map(function(pt) { 95 | /*jslint evil: true */ 96 | var _getXY = new Function('pt', 'return [pt' + format[0] + ',' + 'pt' + format[1] + '];'); 97 | return _getXY(pt); 98 | }); 99 | } 100 | 101 | function _xyToFormat(pointset, format) { 102 | if (format === undefined) { 103 | return pointset; 104 | } 105 | return pointset.map(function(pt) { 106 | /*jslint evil: true */ 107 | var _getObj = new Function('pt', 'var o = {}; o' + format[0] + '= pt[0]; o' + format[1] + '= pt[1]; return o;'); 108 | return _getObj(pt); 109 | }); 110 | } 111 | 112 | function _sortByX(pointset) { 113 | return pointset.sort(function(a, b) { 114 | if (a[0] == b[0]) { 115 | return a[1] - b[1]; 116 | } else { 117 | return a[0] - b[0]; 118 | } 119 | }); 120 | } 121 | 122 | function _getMaxY(pointset) { 123 | var maxY = -Infinity; 124 | for (var i = pointset.length - 1; i >= 0; i--) { 125 | if (pointset[i][1] > maxY) { 126 | maxY = pointset[i][1]; 127 | } 128 | } 129 | return maxY; 130 | } 131 | 132 | function _upperTangent(pointset) { 133 | var lower = []; 134 | for (var l = 0; l < pointset.length; l++) { 135 | while (lower.length >= 2 && (_cross(lower[lower.length - 2], lower[lower.length - 1], pointset[l]) <= 0)) { 136 | lower.pop(); 137 | } 138 | lower.push(pointset[l]); 139 | } 140 | lower.pop(); 141 | return lower; 142 | } 143 | 144 | function _lowerTangent(pointset) { 145 | var reversed = pointset.reverse(), 146 | upper = []; 147 | for (var u = 0; u < reversed.length; u++) { 148 | while (upper.length >= 2 && (_cross(upper[upper.length - 2], upper[upper.length - 1], reversed[u]) <= 0)) { 149 | upper.pop(); 150 | } 151 | upper.push(reversed[u]); 152 | } 153 | upper.pop(); 154 | return upper; 155 | } 156 | 157 | function _cross(o, a, b) { 158 | return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]); 159 | } 160 | 161 | function _sqLength(a, b) { 162 | return Math.pow(b[0] - a[0], 2) + Math.pow(b[1] - a[1], 2); 163 | } 164 | 165 | function _cos(o, a, b) { 166 | var aShifted = [a[0] - o[0], a[1] - o[1]], 167 | bShifted = [b[0] - o[0], b[1] - o[1]], 168 | sqALen = _sqLength(o, a), 169 | sqBLen = _sqLength(o, b), 170 | dot = aShifted[0] * bShifted[0] + aShifted[1] * bShifted[1]; 171 | 172 | return dot / Math.sqrt(sqALen * sqBLen); 173 | } 174 | 175 | function _intersect(segment, pointset) { 176 | for (var i = 0; i < pointset.length - 1; i++) { 177 | var seg = [pointset[i], pointset[i + 1]]; 178 | if (segment[0][0] === seg[0][0] && segment[0][1] === seg[0][1] || 179 | segment[0][0] === seg[1][0] && segment[0][1] === seg[1][1]) { 180 | continue; 181 | } 182 | if (intersect(segment, seg)) { 183 | return true; 184 | } 185 | } 186 | return false; 187 | } 188 | 189 | function _bBoxAround(edge, boxSize) { 190 | var minX, maxX, minY, maxY; 191 | 192 | if (edge[0][0] < edge[1][0]) { 193 | minX = edge[0][0] - boxSize; 194 | maxX = edge[1][0] + boxSize; 195 | } else { 196 | minX = edge[1][0] - boxSize; 197 | maxX = edge[0][0] + boxSize; 198 | } 199 | 200 | if (edge[0][1] < edge[1][1]) { 201 | minY = edge[0][1] - boxSize; 202 | maxY = edge[1][1] + boxSize; 203 | } else { 204 | minY = edge[1][1] - boxSize; 205 | maxY = edge[0][1] + boxSize; 206 | } 207 | 208 | return [ 209 | minX, minY, // tl 210 | maxX, maxY // br 211 | ]; 212 | } 213 | 214 | function _midPoint(edge, innerPoints, convex) { 215 | var point = null, 216 | angle1Cos = MAX_CONCAVE_ANGLE_COS, 217 | angle2Cos = MAX_CONCAVE_ANGLE_COS, 218 | a1Cos, a2Cos; 219 | 220 | for (var i = 0; i < innerPoints.length; i++) { 221 | a1Cos = _cos(edge[0], edge[1], innerPoints[i]); 222 | a2Cos = _cos(edge[1], edge[0], innerPoints[i]); 223 | 224 | if (a1Cos > angle1Cos && a2Cos > angle2Cos && 225 | !_intersect([edge[0], innerPoints[i]], convex) && 226 | !_intersect([edge[1], innerPoints[i]], convex)) { 227 | 228 | angle1Cos = a1Cos; 229 | angle2Cos = a2Cos; 230 | point = innerPoints[i]; 231 | } 232 | } 233 | 234 | return point; 235 | } 236 | 237 | function _concave(convex, maxSqEdgeLen, maxSearchBBoxSize, grid) { 238 | var edge, 239 | border, 240 | bBoxSize, 241 | midPoint, 242 | bBoxAround, 243 | midPointInserted = false; 244 | 245 | for (var i = 0; i < convex.length - 1; i++) { 246 | edge = [convex[i], convex[i + 1]]; 247 | 248 | if (_sqLength(edge[0], edge[1]) < maxSqEdgeLen) { continue; } 249 | 250 | border = 0; 251 | bBoxSize = MIN_SEARCH_BBOX_SIZE; 252 | bBoxAround = _bBoxAround(edge, bBoxSize); 253 | do { 254 | bBoxAround = grid.addBorder2Bbox(bBoxAround, border); 255 | bBoxSize = bBoxAround[2] - bBoxAround[0]; 256 | midPoint = _midPoint(edge, grid.rangePoints(bBoxAround), convex); 257 | border++; 258 | } while (midPoint === null && maxSearchBBoxSize > bBoxSize); 259 | 260 | if (midPoint !== null) { 261 | convex.splice(i + 1, 0, midPoint); 262 | grid.removePoint(midPoint); 263 | midPointInserted = true; 264 | } 265 | } 266 | 267 | if (midPointInserted) { 268 | return _concave(convex, maxSqEdgeLen, maxSearchBBoxSize, grid); 269 | } 270 | 271 | return convex; 272 | } 273 | 274 | function hull(pointset, concavity, format) { 275 | var lower, upper, convex, 276 | innerPoints, 277 | maxSearchBBoxSize, 278 | maxEdgeLen = concavity || 20; 279 | 280 | if (pointset.length < 4) { 281 | return pointset; 282 | } 283 | 284 | pointset = _sortByX(_formatToXy(pointset, format)); 285 | upper = _upperTangent(pointset); 286 | lower = _lowerTangent(pointset); 287 | convex = lower.concat(upper); 288 | convex.push(pointset[0]); 289 | 290 | maxSearchBBoxSize = Math.max(pointset[pointset.length - 1][0], _getMaxY(convex)) * MAX_SEARCH_BBOX_SIZE_PERCENT; 291 | innerPoints = pointset.filter(function(pt) { 292 | return convex.indexOf(pt) < 0; 293 | }); 294 | 295 | return _xyToFormat(_concave(convex, Math.pow(maxEdgeLen, 2), maxSearchBBoxSize, grid(innerPoints)), format); 296 | } 297 | 298 | var MAX_CONCAVE_ANGLE_COS = Math.cos(90 / (180 / Math.PI)); // angle = 90 deg 299 | var MIN_SEARCH_BBOX_SIZE = 5; 300 | var MAX_SEARCH_BBOX_SIZE_PERCENT = 0.8; 301 | 302 | module.exports = hull; 303 | },{"./grid.js":1,"./intersect.js":3}],3:[function(require,module,exports){ 304 | function ccw(x1, y1, x2, y2, x3, y3) { 305 | var cw = ((y3 - y1) * (x2 - x1)) - ((y2 - y1) * (x3 - x1)); 306 | return cw > 0 ? true : cw < 0 ? false : true; // colinear 307 | } 308 | 309 | function intersect(seg1, seg2) { 310 | var x1 = seg1[0][0], y1 = seg1[0][1], 311 | x2 = seg1[1][0], y2 = seg1[1][1], 312 | x3 = seg2[0][0], y3 = seg2[0][1], 313 | x4 = seg2[1][0], y4 = seg2[1][1]; 314 | return ccw(x1, y1, x3, y3, x4, y4) !== ccw(x2, y2, x3, y3, x4, y4) && ccw(x1, y1, x2, y2, x3, y3) !== ccw(x1, y1, x2, y2, x4, y4); 315 | } 316 | 317 | module.exports = intersect; 318 | },{}]},{},[2])(2) 319 | }); -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | var Editor; // global variable 2 | 3 | function init(){ 4 | var canvas = document.getElementById("canvas"); 5 | canvas.width = window.innerWidth * 0.8; 6 | canvas.height = window.innerHeight * 0.8; 7 | 8 | // create instance of physics editor 9 | Editor = new PhysicsEditor(canvas); 10 | 11 | // cached variables 12 | var viewport = Editor.getViewport(), 13 | lastElementSelected; 14 | 15 | // to avoid viewport events while editing selection properties 16 | document.addEventListener("mousedown", function(e){ 17 | lastElementSelected = e.target; 18 | }); 19 | 20 | // key events 21 | window.addEventListener("keydown", function(e){ 22 | if (lastElementSelected == viewport.canvas) 23 | viewport.onKeyDown(e); 24 | }); 25 | window.addEventListener("keyup", function(e){ 26 | if (lastElementSelected == viewport.canvas) 27 | viewport.onKeyUp(e) 28 | }); 29 | window.addEventListener("resize", function(e){ 30 | // canvas.width = window.innerWidth * 0.8; 31 | // canvas.height = window.innerHeight * 0.8; 32 | // Editor.viewport.getRenderer().setStageWidthHeight(canvas.width, canvas.height); 33 | }); 34 | window.onbeforeunload = function(){ 35 | return "All Unsaved Changes Would Be Lost"; 36 | } 37 | } 38 | 39 | // update loop 40 | function render() { 41 | Editor.viewport.draw(Editor.getGameView()); 42 | setTimeout(render, 1000.0 / 60.0); // update at 60 fps 43 | } 44 | //-------------------------------------------// 45 | 46 | // launch the editor 47 | init(); 48 | setTimeout(render, 1000.0 / 60.0); // update at 60 fps -------------------------------------------------------------------------------- /resources/autotrace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/autotrace.png -------------------------------------------------------------------------------- /resources/editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/editor.png -------------------------------------------------------------------------------- /resources/editor_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/editor_small.png -------------------------------------------------------------------------------- /resources/level.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/level.jpg -------------------------------------------------------------------------------- /resources/loaders/Box2dWeb/WorldLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Amit Kumar Mehar 3 | 4 | This software is provided 'as-is', without any express or implied 5 | warranty. In no event will the authors be held liable for any damages 6 | arising from the use of this software. 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 1. The origin of this software must not be misrepresented; you must not 11 | claim that you wrote the original software. If you use this software 12 | in a product, an acknowledgment in the product documentation would be 13 | appreciated but is not required. 14 | 2. Altered source versions must be plainly marked as such, and must not be 15 | misrepresented as being the original software. 16 | 3. This notice may not be removed or altered from any source distribution. 17 | */ 18 | 19 | var b2Vec2 = Box2D.Common.Math.b2Vec2, 20 | b2AABB = Box2D.Collision.b2AABB, 21 | b2BodyDef = Box2D.Dynamics.b2BodyDef, 22 | b2Body = Box2D.Dynamics.b2Body, 23 | b2FixtureDef = Box2D.Dynamics.b2FixtureDef, 24 | b2Fixture = Box2D.Dynamics.b2Fixture, 25 | b2World = Box2D.Dynamics.b2World, 26 | b2MassData = Box2D.Collision.Shapes.b2MassData, 27 | b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape, 28 | b2CircleShape = Box2D.Collision.Shapes.b2CircleShape, 29 | b2DebugDraw = Box2D.Dynamics.b2DebugDraw, 30 | b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef, 31 | b2DistanceJointDef = Box2D.Dynamics.Joints.b2DistanceJointDef, 32 | b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef, 33 | b2WeldJointDef = Box2D.Dynamics.Joints.b2WeldJointDef, 34 | b2PulleyJointDef = Box2D.Dynamics.Joints.b2PulleyJointDef; 35 | b2GearJointDef = Box2D.Dynamics.Joints.b2GearJointDef, 36 | b2PrismaticJointDef = Box2D.Dynamics.Joints.b2PrismaticJointDef; 37 | 38 | var WorldLoader = (function(){ 39 | 40 | /** for bodies with texture data */ 41 | function GameObject () { 42 | this.body; 43 | this.sprite; 44 | this.spriteData = []; 45 | } 46 | 47 | function WorldLoader (){ 48 | this.loadedBodies = []; // to store box2d bodies created to use when creating joints 49 | this.loadedJoints = []; // to store box2d joints created 50 | this.gameObjects = []; // to store gameobjects created 51 | this.offsetX = 0; 52 | this.offsetY = 0; 53 | } 54 | 55 | WorldLoader.prototype.reset = function(){ 56 | if (this.loadedBodies.length > 0){ 57 | delete(this.loadedBodies); 58 | } 59 | this.loadedBodies = []; 60 | 61 | if (this.loadedJoints.length > 0){ 62 | delete(this.loadedJoints); 63 | } 64 | this.loadedJoints = []; 65 | 66 | if (this.gameObjects.length > 0){ 67 | delete(this.gameObjects); 68 | } 69 | this.gameObjects = []; 70 | }; 71 | 72 | WorldLoader.prototype.loadJsonScene = function(scene, world){ 73 | this.reset(); 74 | 75 | // load bodies 76 | for (var i = 0; i < scene.bodies.length; i++){ 77 | this.createBody(scene.bodies[i], world); 78 | } 79 | 80 | // load joints 81 | for (var i = 0; i < scene.joints.length; i++){ 82 | this.createJoint(scene.joints[i], world); 83 | } 84 | }; 85 | 86 | WorldLoader.prototype.createBody = function(b, world){ 87 | var pB = b; 88 | 89 | var bodyDef = new b2BodyDef; 90 | bodyDef.type = pB.type; 91 | bodyDef.position.Set(pB.position[0] / 30, pB.position[1] / 30); 92 | var body = world.CreateBody(bodyDef); 93 | body.SetAngle(pB.rotation * Math.PI / 180); 94 | body.SetBullet(pB.isBullet); 95 | body.SetFixedRotation(pB.isFixedRotation); 96 | body.SetLinearDamping(pB.linearDamping); 97 | body.SetAngularDamping(pB.angularDamping); 98 | 99 | this.loadedBodies.push(body); 100 | 101 | if (pB.texture){ 102 | this.createGameObject(pB.texture, pB.textureData, body); 103 | } 104 | 105 | for (var i = 0; i < pB.fixtures.length; i++){ 106 | var f = pB.fixtures[i]; 107 | var fixture = new b2FixtureDef; 108 | fixture.density = f.density; 109 | fixture.restitution = f.restitution; 110 | fixture.friction = f.friction; 111 | fixture.isSensor = f.isSensor; 112 | fixture.filter.maskBits = f.maskBits; 113 | fixture.filter.categoryBits = f.categoryBits; 114 | fixture.filter.groupIndex = f.groupIndex; 115 | 116 | for (var j = 0; j < f.shapes.length; j++){ 117 | var s = f.shapes[j]; 118 | if (s.type == Shape.SHAPE_BOX){ 119 | var shape = new b2PolygonShape; 120 | shape.SetAsBox(s.width / 60, s.height / 60); 121 | for(var k = 0; k < shape.m_vertices.length; k++){ 122 | shape.m_vertices[k].x += s.position[0] / 30; 123 | shape.m_vertices[k].y += s.position[1] / 30; 124 | } 125 | fixture.shape = shape; 126 | body.CreateFixture(fixture); 127 | } 128 | else if (s.type == Shape.SHAPE_CIRCLE){ 129 | var shape = new b2CircleShape(s.radius * 2 / 30); 130 | shape.SetLocalPosition(new b2Vec2(s.position[0] / 30, s.position[1] / 30)); 131 | fixture.shape = shape; 132 | body.CreateFixture(fixture); 133 | } 134 | else if (s.type == Shape.SHAPE_POLYGON){ 135 | var shape = new b2PolygonShape; 136 | var verts = []; 137 | for (var k = 0; k < s.vertices.length; k++){ 138 | var vert = new b2Vec2(s.position[0] / 30 + s.vertices[k][0] / 30, s.position[1] / 30 + s.vertices[k][1] / 30); 139 | verts.push(vert); 140 | } 141 | shape.SetAsArray(verts); 142 | fixture.shape = shape; 143 | body.CreateFixture(fixture); 144 | } 145 | else if (s.type == Shape.SHAPE_CHAIN){ 146 | var shape = new b2PolygonShape; 147 | fixture.shape = shape; 148 | for (var k = 0; k < s.vertices.length; k++){ 149 | var vert1 = new b2Vec2(s.position[0] / 30 + s.vertices[k][0] / 30, s.position[1] / 30 + s.vertices[k][1] / 30); 150 | var vert2 = new b2Vec2(s.position[0] / 30 + s.vertices[k < s.vertices.length - 1 ? k + 1 : 0][0] / 30, s.position[1] / 30 + s.vertices[k < s.vertices.length - 1 ? k + 1 : 0][1] / 30); 151 | shape.SetAsEdge(vert1, vert2); 152 | body.CreateFixture(fixture); 153 | } 154 | } 155 | } 156 | } 157 | }; 158 | 159 | WorldLoader.prototype.createJoint = function(j, world){ 160 | if (j.jointType == Joint.JOINT_DISTANCE){ 161 | var jointDef = new b2DistanceJointDef; 162 | jointDef.bodyA = this.loadedBodies[j.bodyA]; 163 | jointDef.bodyB = this.loadedBodies[j.bodyB]; 164 | jointDef.localAnchorA = new b2Vec2(j.localAnchorA[0] / 30, j.localAnchorA[1] / 30); 165 | jointDef.localAnchorB = new b2Vec2(j.localAnchorB[0] / 30, j.localAnchorB[1] / 30); 166 | jointDef.collideConnected = j.collideConnected; 167 | jointDef.length = j.length / 30; 168 | jointDef.dampingRatio = j.dampingRatio; 169 | jointDef.frequencyHz = j.frequencyHZ; 170 | this.loadedJoints.push(world.CreateJoint(jointDef)); 171 | } 172 | else if (j.jointType == Joint.JOINT_WELD){ 173 | var jointDef = new b2WeldJointDef; 174 | jointDef.bodyA = this.loadedBodies[j.bodyA]; 175 | jointDef.bodyB = this.loadedBodies[j.bodyB]; 176 | jointDef.localAnchorA = new b2Vec2(j.localAnchorA[0] / 30, j.localAnchorA[1] / 30); 177 | jointDef.localAnchorB = new b2Vec2(j.localAnchorB[0] / 30, j.localAnchorB[1] / 30); 178 | jointDef.collideConnected = j.collideConnected; 179 | jointDef.referenceAngle = j.referenceAngle * Math.PI / 180; 180 | this.loadedJoints.push(world.CreateJoint(jointDef)); 181 | } 182 | else if (j.jointType == Joint.JOINT_REVOLUTE){ 183 | var jointDef = new b2RevoluteJointDef; 184 | jointDef.bodyA = this.loadedBodies[j.bodyA]; 185 | jointDef.bodyB = this.loadedBodies[j.bodyB]; 186 | jointDef.localAnchorA = new b2Vec2(j.localAnchorA[0] / 30, j.localAnchorA[1] / 30); 187 | jointDef.localAnchorB = new b2Vec2(j.localAnchorB[0] / 30, j.localAnchorB[1] / 30); 188 | jointDef.collideConnected = j.collideConnected; 189 | jointDef.enableLimit = j.enableLimit; 190 | jointDef.enableMotor = j.enableMotor; 191 | jointDef.lowerAngle = j.lowerAngle * Math.PI / 180; 192 | jointDef.maxMotorTorque = j.maxMotorTorque; 193 | jointDef.motorSpeed = j.motorSpeed; 194 | jointDef.referenceAngle = j.referenceAngle * Math.PI / 180; 195 | jointDef.upperAngle = j.upperAngle * Math.PI / 180; 196 | this.loadedJoints.push(world.CreateJoint(jointDef)); 197 | } 198 | else if (j.jointType == Joint.JOINT_PULLEY){ 199 | var jointDef = new b2PulleyJointDef; 200 | jointDef.bodyA = this.loadedBodies[j.bodyA]; 201 | jointDef.bodyB = this.loadedBodies[j.bodyB]; 202 | jointDef.localAnchorA = new b2Vec2(j.localAnchorA[0] / 30, j.localAnchorA[1] / 30); 203 | jointDef.localAnchorB = new b2Vec2(j.localAnchorB[0] / 30, j.localAnchorB[1] / 30); 204 | jointDef.collideConnected = j.collideConnected; 205 | jointDef.groundAnchorA = new b2Vec2(j.groundAnchorA[0] / 30, j.groundAnchorA[1] / 30); 206 | jointDef.groundAnchorB = new b2Vec2(j.groundAnchorB[0] / 30, j.groundAnchorB[1] / 30); 207 | jointDef.lengthA = j.lengthA / 30; 208 | jointDef.lengthB = j.lengthB / 30; 209 | jointDef.maxLengthA = j.maxLengthA / 30; 210 | jointDef.maxLengthB = j.maxLengthB / 30; 211 | jointDef.ratio = j.ratio; 212 | this.loadedJoints.push(world.CreateJoint(jointDef)); 213 | } 214 | else if (j.jointType == Joint.JOINT_GEAR){ 215 | var jointDef = new b2GearJointDef; 216 | jointDef.bodyA = this.loadedBodies[j.bodyA]; 217 | jointDef.bodyB = this.loadedBodies[j.bodyB]; 218 | jointDef.joint1 = this.loadedJoints[j.joint1]; 219 | jointDef.joint2 = this.loadedJoints[j.joint2]; 220 | jointDef.collideConnected = j.collideConnected; 221 | jointDef.ratio = j.ratio; 222 | this.loadedJoints.push(world.CreateJoint(jointDef)); 223 | } 224 | else if (j.jointType == Joint.JOINT_PRISMATIC){ 225 | var jointDef = new b2PrismaticJointDef; 226 | jointDef.bodyA = this.loadedBodies[j.bodyA]; 227 | jointDef.bodyB = this.loadedBodies[j.bodyB]; 228 | jointDef.localAnchorA = new b2Vec2(j.localAnchorA[0] / 30, j.localAnchorA[1] / 30); 229 | jointDef.localAnchorB = new b2Vec2(j.localAnchorB[0] / 30, j.localAnchorB[1] / 30); 230 | jointDef.localAxisA = new b2Vec2(j.localAxisA[0], j.localAxisA[1]); 231 | jointDef.collideConnected = j.collideConnected; 232 | jointDef.enableLimit = j.enableLimit; 233 | jointDef.enableMotor = j.enableMotor; 234 | jointDef.lowerTranslation = j.lowerTranslation / 30; 235 | jointDef.maxMotorForce = j.maxMotorForce; 236 | jointDef.motorSpeed = j.motorSpeed; 237 | jointDef.referenceAngle = j.referenceAngle * Math.PI / 180; 238 | jointDef.upperTranslation = j.upperTranslation / 30; 239 | this.loadedJoints.push(world.CreateJoint(jointDef)); 240 | } 241 | 242 | // wheel joint is not supported in box2d-web 243 | else if (j.jointType == Joint.JOINT_WHEEL){ 244 | // for (var f = this.loadedBodies[j.bodyA].GetFixtureList(); f != null; f = f.GetNext()){ 245 | // f.m_filter.groupIndex = -1; 246 | // } 247 | // for (var f = this.loadedBodies[j.bodyB].GetFixtureList(); f != null; f = f.GetNext()){ 248 | // f.m_filter.groupIndex = -1; 249 | // } 250 | // create a new body to use as axle 251 | // var shape = new b2CircleShape(10 / 30); 252 | // var fixture = new b2FixtureDef; 253 | // fixture.density = 0; 254 | // fixture.restitution = 0; 255 | // fixture.friction = 0; 256 | // fixture.shape = shape; 257 | // fixture.isSensor = true; 258 | // var bodyDef = new b2BodyDef; 259 | // bodyDef.type = b2Body.b2_dynamicBody; 260 | // bodyDef.position.Set(this.loadedBodies[j.bodyB].GetPosition().x + j.localAnchorB[0] / 30, this.loadedBodies[j.bodyB].GetPosition().y + j.localAnchorB[1] / 30); 261 | // var axle = world.CreateBody(bodyDef); 262 | // axle.CreateFixture(fixture); 263 | 264 | // var revJointDef = new b2RevoluteJointDef; 265 | // revJointDef.bodyA = this.loadedBodies[j.bodyA]; 266 | // revJointDef.bodyB = axle; 267 | // revJointDef.localAnchorA = new b2Vec2(axle.GetPosition().x - this.loadedBodies[j.bodyA].GetPosition().x, 268 | // axle.GetPosition().y - this.loadedBodies[j.bodyA].GetPosition().y);//new b2Vec2(j.localAnchorA[0] / 30, j.localAnchorA[1] / 30); 269 | // revJointDef.localAnchorB = new b2Vec2(0, 0); 270 | // revJointDef.collideConnected = false; 271 | // revJointDef.enableMotor = j.enableMotor; 272 | // revJointDef.maxMotorTorque = j.maxMotorTorque; 273 | // revJointDef.motorSpeed = j.motorSpeed; 274 | // world.CreateJoint(revJointDef); 275 | 276 | // var distJointDef = new b2DistanceJointDef; 277 | // distJointDef.bodyA = axle; 278 | // distJointDef.bodyB = this.loadedBodies[j.bodyB]; 279 | // distJointDef.localAnchorA = new b2Vec2(0, 0); 280 | // distJointDef.localAnchorB = new b2Vec2(0,0); 281 | // distJointDef.collideConnected = false; 282 | // distJointDef.length = 1 / 30; 283 | // distJointDef.dampingRatio = j.dampingRatio; 284 | // distJointDef.frequencyHz = j.frequencyHZ; 285 | // world.CreateJoint(distJointDef); 286 | } 287 | 288 | // not supported bu box2d-web 289 | else if (j.jointType == Joint.JOINT_ROPE){ 290 | } 291 | }; 292 | 293 | WorldLoader.prototype.createGameObject = function(texture, textureData, body){ 294 | var gameObject = new GameObject(); 295 | gameObject.sprite = new Image(); 296 | gameObject.sprite.src = texture; 297 | 298 | if (textureData.length == 2){ 299 | gameObject.sprite.width = textureData[0]; 300 | gameObject.sprite.height = textureData[1]; 301 | } 302 | else { 303 | gameObject.spriteData[0] = textureData[0]; 304 | gameObject.spriteData[1] = textureData[1]; 305 | gameObject.spriteData[2] = textureData[2]; 306 | gameObject.spriteData[3] = textureData[3]; 307 | gameObject.spriteData[4] = textureData[4]; 308 | gameObject.spriteData[5] = textureData[5]; 309 | } 310 | 311 | gameObject.body = body; 312 | this.gameObjects.push(gameObject); 313 | }; 314 | 315 | return new WorldLoader; 316 | }); -------------------------------------------------------------------------------- /resources/loaders/Cocos2d-x/Box2dWorldLoader.cpp: -------------------------------------------------------------------------------- 1 | #include "Box2dWorldLoader.h" 2 | 3 | Box2dWorldLoader::Box2dWorldLoader() 4 | { 5 | } 6 | 7 | Box2dWorldLoader::~Box2dWorldLoader() 8 | { 9 | } 10 | 11 | void Box2dWorldLoader::setOffset(float x, float y){ 12 | offsetX = x; 13 | offsetY = y; 14 | } 15 | 16 | void Box2dWorldLoader::loadJsonScene(const char* file, b2World *world){ 17 | cocos2d::FileUtils::getInstance()->addSearchPath("Scenes"); 18 | std::string fullPath = cocos2d::FileUtils::getInstance()->fullPathForFilename(file); 19 | ssize_t bufferSize; 20 | const char* mFileData = (const char*)cocos2d::FileUtils::getInstance()->getFileData(fullPath.c_str(), "r", &bufferSize); 21 | std::string scene(mFileData, bufferSize); 22 | 23 | rapidjson::Document jsonScene; 24 | 25 | if (jsonScene.Parse<0>(scene.c_str()).HasParseError() == false){ 26 | rapidjson::Value& jsonBodies = jsonScene["bodies"]; 27 | loadBodies(jsonBodies, world); 28 | rapidjson::Value& jsonJoints = jsonScene["joints"]; 29 | loadJoints(jsonJoints, world); 30 | } 31 | else { 32 | cocos2d::log("could not parse the scene!"); 33 | } 34 | } 35 | void Box2dWorldLoader::loadBodies(rapidjson::Value &jsonBodies, b2World *world){ 36 | for (int i = 0; i < jsonBodies.Size(); i++){ 37 | rapidjson::Value &jsonBody = jsonBodies[i]; 38 | 39 | rapidjson::Value &pos = jsonBody["position"]; 40 | b2Vec2 position; 41 | position.x = offsetX / PTM_RATIO + pos[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO; 42 | position.y = offsetY / PTM_RATIO - pos[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO; 43 | float rotation = -jsonBody["rotation"].GetDouble() * M_PI / 180.0, 44 | linearDamping = jsonBody["linearDamping"].GetDouble(), 45 | angularDamping = jsonBody["angularDamping"].GetDouble(); 46 | 47 | bool isBullet = jsonBody["isBullet"].GetBool(), 48 | isFixedRotation = jsonBody["isFixedRotation"].GetBool(); 49 | int bodyType = jsonBody["type"].GetInt(); 50 | 51 | std::string jsonUserData = jsonBody["userData"].GetString(); 52 | std::string jsonTextureData = ""; 53 | if (jsonBody["texture"].IsString()){ 54 | jsonTextureData = jsonBody["texture"].GetString(); 55 | } 56 | b2BodyUserData *userData = new b2BodyUserData(jsonUserData.c_str(), jsonTextureData.c_str()); 57 | 58 | b2BodyDef *bodyDef = new b2BodyDef(); 59 | if (bodyType == 0){ 60 | bodyDef->type = b2_staticBody; 61 | } 62 | else if (bodyType == 1){ 63 | bodyDef->type = b2_kinematicBody; 64 | } 65 | else if (bodyType == 2){ 66 | bodyDef->type = b2_dynamicBody; 67 | } 68 | 69 | b2Body *body = world->CreateBody(bodyDef); 70 | body->SetUserData(userData); 71 | body->SetTransform(position, rotation); 72 | body->SetBullet(isBullet); 73 | body->SetFixedRotation(isFixedRotation); 74 | body->SetLinearDamping(linearDamping); 75 | body->SetAngularDamping(angularDamping); 76 | 77 | rapidjson::Value &jsonFixtures = jsonBody["fixtures"]; 78 | std::vector fixtures = loadJsonFixture(jsonFixtures); 79 | for (int j = 0; j < fixtures.size(); j++){ 80 | body->CreateFixture(fixtures.at(j)); 81 | } 82 | 83 | loadedBodies.push_back(body); 84 | } 85 | } 86 | 87 | std::vector Box2dWorldLoader::loadJsonFixture(rapidjson::Value &jsonFixtures){ 88 | std::vector fixtures; 89 | for (int i = 0; i < jsonFixtures.Size(); i++){ 90 | rapidjson::Value &jsonFixture = jsonFixtures[i]; 91 | 92 | float density = jsonFixture["density"].GetDouble(), 93 | restitution = jsonFixture["restitution"].GetDouble(), 94 | friction = jsonFixture["friction"].GetDouble(); 95 | int maskBits = jsonFixture["maskBits"].GetInt(), 96 | categoryBits = jsonFixture["categoryBits"].GetInt(), 97 | groupIndex = jsonFixture["groupIndex"].GetInt(); 98 | bool isSensor = jsonFixture["isSensor"].GetBool(); 99 | 100 | rapidjson::Value &jsonShapes = jsonFixture["shapes"]; 101 | for (int j = 0; j < jsonShapes.Size(); j++){ 102 | b2FixtureDef *fixtureDef = new b2FixtureDef(); 103 | fixtureDef->isSensor = isSensor; 104 | fixtureDef->density = density; 105 | fixtureDef->friction = friction; 106 | fixtureDef->restitution = restitution; 107 | 108 | fixtureDef->filter.maskBits = maskBits; 109 | fixtureDef->filter.categoryBits = categoryBits; 110 | fixtureDef->filter.groupIndex = groupIndex; 111 | 112 | rapidjson::Value &jsonShape = jsonShapes[j]; 113 | int shapeType = jsonShape["type"].GetInt(); 114 | 115 | rapidjson::Value &pos = jsonShape["position"]; 116 | b2Vec2 position; 117 | position.x = pos[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO; 118 | position.y = -pos[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO; 119 | 120 | if (shapeType == SHAPE_BOX){ 121 | float width = jsonShape["width"].GetDouble() / PTM_RATIO; 122 | float height = jsonShape["height"].GetDouble() / PTM_RATIO; 123 | b2PolygonShape *shape = new b2PolygonShape(); 124 | shape->SetAsBox(width / 2, height / 2, position, 0); 125 | fixtureDef->shape = shape; 126 | } 127 | else if (shapeType == SHAPE_CIRCLE){ 128 | float radius = jsonShape["radius"].GetDouble() * 2 / PTM_RATIO; 129 | b2CircleShape *shape = new b2CircleShape(); 130 | shape->m_radius = radius; 131 | shape->m_p.Set(position.x, position.y); 132 | fixtureDef->shape = shape; 133 | } 134 | else if (shapeType == SHAPE_POLYGON){ 135 | rapidjson::Value &jsonVertices = jsonShape["vertices"]; 136 | int vertCount = jsonVertices.Size(); 137 | b2Vec2 *vertices = new b2Vec2[vertCount]; 138 | for (int k = 0; k < vertCount; k++){ 139 | rapidjson::Value &jsonVert = jsonVertices[k]; 140 | vertices[k] = b2Vec2(position.x + jsonVert[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, 141 | position.y - jsonVert[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 142 | } 143 | b2PolygonShape *shape = new b2PolygonShape(); 144 | shape->Set(vertices, vertCount); 145 | fixtureDef->shape = shape; 146 | } 147 | else if (shapeType == SHAPE_CHAIN){ 148 | rapidjson::Value &jsonVertices = jsonShape["vertices"]; 149 | int vertCount = jsonVertices.Size(); 150 | b2ChainShape *shape = new b2ChainShape(); 151 | for (int k = 0; k < vertCount; k++){ 152 | rapidjson::Value &jsonVert = jsonVertices[k]; 153 | b2Vec2 nextVert = b2Vec2(position.x + jsonVert[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, 154 | position.y - jsonVert[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 155 | shape->SetNextVertex(nextVert); 156 | } 157 | fixtureDef->shape = shape; 158 | } 159 | 160 | fixtures.push_back(fixtureDef); 161 | } 162 | } 163 | return fixtures; 164 | } 165 | 166 | void Box2dWorldLoader::loadJoints(rapidjson::Value &jsonJoints, b2World *world){ 167 | for (int i = 0; i < jsonJoints.Size(); i++){ 168 | rapidjson::Value &jsonJoint = jsonJoints[i]; 169 | 170 | int jointType = jsonJoint["jointType"].GetInt(); 171 | 172 | if (jointType == JOINT_DISTANCE){ 173 | b2DistanceJointDef *jointDef = new b2DistanceJointDef(); 174 | jointDef->bodyA = loadedBodies.at(jsonJoint["bodyA"].GetInt()); 175 | jointDef->bodyB = loadedBodies.at(jsonJoint["bodyB"].GetInt()); 176 | rapidjson::Value &localAnchorA = jsonJoint["localAnchorA"]; 177 | rapidjson::Value &localAnchorB = jsonJoint["localAnchorB"]; 178 | jointDef->localAnchorA.Set(localAnchorA[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorA[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 179 | jointDef->localAnchorB.Set(localAnchorB[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorB[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 180 | jointDef->collideConnected = jsonJoint["collideConnected"].GetBool(); 181 | jointDef->length = jsonJoint["length"].GetDouble() / PTM_RATIO; 182 | jointDef->dampingRatio = jsonJoint["dampingRatio"].GetDouble(); 183 | jointDef->frequencyHz = jsonJoint["frequencyHZ"].GetDouble(); 184 | 185 | b2DistanceJoint *joint = (b2DistanceJoint*) world->CreateJoint(jointDef); 186 | 187 | std::string jsonUserData = jsonJoint["userData"].GetString(); 188 | char *userData = new char[strlen(jsonUserData.c_str()) + 1]; 189 | strcpy(userData, jsonUserData.c_str()); 190 | joint->SetUserData(userData); 191 | loadedJoints.push_back(joint); 192 | } 193 | else if (jointType == JOINT_WELD){ 194 | b2WeldJointDef *jointDef = new b2WeldJointDef(); 195 | jointDef->bodyA = loadedBodies.at(jsonJoint["bodyA"].GetInt()); 196 | jointDef->bodyB = loadedBodies.at(jsonJoint["bodyB"].GetInt()); 197 | rapidjson::Value &localAnchorA = jsonJoint["localAnchorA"]; 198 | rapidjson::Value &localAnchorB = jsonJoint["localAnchorB"]; 199 | jointDef->localAnchorA.Set(localAnchorA[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorA[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 200 | jointDef->localAnchorB.Set(localAnchorB[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorB[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 201 | jointDef->collideConnected = jsonJoint["collideConnected"].GetBool(); 202 | jointDef->referenceAngle = -jsonJoint["referenceAngle"].GetDouble() * M_PI / 180; 203 | 204 | b2WeldJoint *joint = (b2WeldJoint*)world->CreateJoint(jointDef); 205 | 206 | std::string jsonUserData = jsonJoint["userData"].GetString(); 207 | char *userData = new char[strlen(jsonUserData.c_str()) + 1]; 208 | strcpy(userData, jsonUserData.c_str()); 209 | 210 | joint->SetUserData(userData); 211 | loadedJoints.push_back(joint); 212 | } 213 | else if (jointType == JOINT_REVOLUTE){ 214 | b2RevoluteJointDef *jointDef = new b2RevoluteJointDef(); 215 | jointDef->bodyA = loadedBodies.at(jsonJoint["bodyA"].GetInt()); 216 | jointDef->bodyB = loadedBodies.at(jsonJoint["bodyB"].GetInt()); 217 | rapidjson::Value &localAnchorA = jsonJoint["localAnchorA"]; 218 | rapidjson::Value &localAnchorB = jsonJoint["localAnchorB"]; 219 | jointDef->localAnchorA.Set(localAnchorA[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorA[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 220 | jointDef->localAnchorB.Set(localAnchorB[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorB[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 221 | jointDef->collideConnected = jsonJoint["collideConnected"].GetBool(); 222 | jointDef->enableLimit = jsonJoint["enableLimit"].GetBool(); 223 | jointDef->referenceAngle = -jsonJoint["referenceAngle"].GetDouble() * M_PI / 180; 224 | jointDef->upperAngle = -jsonJoint["lowerAngle"].GetDouble() * M_PI / 180; 225 | jointDef->lowerAngle = -jsonJoint["upperAngle"].GetDouble() * M_PI / 180; 226 | jointDef->enableMotor = jsonJoint["enableMotor"].GetBool(); 227 | jointDef->motorSpeed = -jsonJoint["motorSpeed"].GetDouble(); 228 | jointDef->maxMotorTorque = jsonJoint["maxMotorTorque"].GetDouble(); 229 | 230 | b2RevoluteJoint *joint = (b2RevoluteJoint*)world->CreateJoint(jointDef); 231 | 232 | std::string jsonUserData = jsonJoint["userData"].GetString(); 233 | char *userData = new char[strlen(jsonUserData.c_str()) + 1]; 234 | strcpy(userData, jsonUserData.c_str()); 235 | joint->SetUserData(userData); 236 | loadedJoints.push_back(joint); 237 | } 238 | else if (jointType == JOINT_WHEEL){ 239 | b2WheelJointDef *jointDef = new b2WheelJointDef(); 240 | jointDef->bodyA = loadedBodies.at(jsonJoint["bodyA"].GetInt()); 241 | jointDef->bodyB = loadedBodies.at(jsonJoint["bodyB"].GetInt()); 242 | rapidjson::Value &localAnchorA = jsonJoint["localAnchorA"]; 243 | rapidjson::Value &localAnchorB = jsonJoint["localAnchorB"]; 244 | rapidjson::Value &localAxisA = jsonJoint["localAxisA"]; 245 | jointDef->localAnchorA.Set(localAnchorA[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorA[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 246 | jointDef->localAnchorB.Set(localAnchorB[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorB[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 247 | jointDef->localAxisA.Set(localAxisA[rapidjson::SizeType(0)].GetDouble(), -localAxisA[rapidjson::SizeType(1)].GetDouble()); 248 | jointDef->collideConnected = jsonJoint["collideConnected"].GetBool(); 249 | jointDef->enableMotor = jsonJoint["enableMotor"].GetBool(); 250 | jointDef->motorSpeed = -jsonJoint["motorSpeed"].GetDouble(); 251 | jointDef->maxMotorTorque = jsonJoint["maxMotorTorque"].GetDouble(); 252 | jointDef->dampingRatio = jsonJoint["dampingRatio"].GetDouble(); 253 | jointDef->frequencyHz = jsonJoint["frequencyHZ"].GetDouble(); 254 | 255 | b2WheelJoint *joint = (b2WheelJoint*)world->CreateJoint(jointDef); 256 | 257 | std::string jsonUserData = jsonJoint["userData"].GetString(); 258 | char *userData = new char[strlen(jsonUserData.c_str()) + 1]; 259 | strcpy(userData, jsonUserData.c_str()); 260 | joint->SetUserData(userData); 261 | loadedJoints.push_back(joint); 262 | } 263 | else if (jointType == JOINT_PULLEY){ 264 | b2PulleyJointDef *jointDef = new b2PulleyJointDef(); 265 | jointDef->bodyA = loadedBodies.at(jsonJoint["bodyA"].GetInt()); 266 | jointDef->bodyB = loadedBodies.at(jsonJoint["bodyB"].GetInt()); 267 | rapidjson::Value &localAnchorA = jsonJoint["localAnchorA"]; 268 | rapidjson::Value &localAnchorB = jsonJoint["localAnchorB"]; 269 | rapidjson::Value &groundAnchorA = jsonJoint["groundAnchorA"]; 270 | rapidjson::Value &groundAnchorB = jsonJoint["groundAnchorB"]; 271 | jointDef->localAnchorA.Set(localAnchorA[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorA[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 272 | jointDef->localAnchorB.Set(localAnchorB[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorB[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 273 | jointDef->groundAnchorA.Set(offsetX / PTM_RATIO + groundAnchorA[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, 274 | offsetY / PTM_RATIO - groundAnchorA[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 275 | jointDef->groundAnchorB.Set(offsetX / PTM_RATIO + groundAnchorB[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, 276 | offsetY / PTM_RATIO - groundAnchorB[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 277 | jointDef->collideConnected = jsonJoint["collideConnected"].GetBool(); 278 | jointDef->lengthA = jsonJoint["lengthA"].GetDouble() / PTM_RATIO; 279 | jointDef->lengthB = jsonJoint["lengthB"].GetDouble() / PTM_RATIO; 280 | jointDef->ratio = jsonJoint["ratio"].GetDouble() / PTM_RATIO; 281 | 282 | b2PulleyJoint *joint = (b2PulleyJoint*)world->CreateJoint(jointDef); 283 | 284 | std::string jsonUserData = jsonJoint["userData"].GetString(); 285 | char *userData = new char[strlen(jsonUserData.c_str()) + 1]; 286 | strcpy(userData, jsonUserData.c_str()); 287 | joint->SetUserData(userData); 288 | loadedJoints.push_back(joint); 289 | } 290 | else if (jointType == JOINT_GEAR){ 291 | b2GearJointDef *jointDef = new b2GearJointDef(); 292 | jointDef->bodyA = loadedBodies.at(jsonJoint["bodyA"].GetInt()); 293 | jointDef->bodyB = loadedBodies.at(jsonJoint["bodyB"].GetInt()); 294 | jointDef->joint1 = loadedJoints.at(jsonJoint["joint1"].GetInt()); 295 | jointDef->joint2 = loadedJoints.at(jsonJoint["joint2"].GetInt()); 296 | jointDef->collideConnected = jsonJoint["collideConnected"].GetBool(); 297 | jointDef->ratio = jsonJoint["ratio"].GetDouble() / PTM_RATIO; 298 | 299 | b2GearJoint *joint = (b2GearJoint*)world->CreateJoint(jointDef); 300 | 301 | std::string jsonUserData = jsonJoint["userData"].GetString(); 302 | char *userData = new char[strlen(jsonUserData.c_str()) + 1]; 303 | strcpy(userData, jsonUserData.c_str()); 304 | joint->SetUserData(userData); 305 | loadedJoints.push_back(joint); 306 | } 307 | else if (jointType == JOINT_PRISMATIC){ 308 | b2PrismaticJointDef *jointDef = new b2PrismaticJointDef(); 309 | jointDef->bodyA = loadedBodies.at(jsonJoint["bodyA"].GetInt()); 310 | jointDef->bodyB = loadedBodies.at(jsonJoint["bodyB"].GetInt()); 311 | rapidjson::Value &localAnchorA = jsonJoint["localAnchorA"]; 312 | rapidjson::Value &localAnchorB = jsonJoint["localAnchorB"]; 313 | rapidjson::Value &localAxisA = jsonJoint["localAxisA"]; 314 | jointDef->localAnchorA.Set(localAnchorA[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorA[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 315 | jointDef->localAnchorB.Set(localAnchorB[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorB[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 316 | jointDef->localAxisA.Set(localAxisA[rapidjson::SizeType(0)].GetDouble(), -localAxisA[rapidjson::SizeType(1)].GetDouble()); 317 | jointDef->collideConnected = jsonJoint["collideConnected"].GetBool(); 318 | jointDef->enableLimit = jsonJoint["enableLimit"].GetBool(); 319 | jointDef->referenceAngle = -jsonJoint["referenceAngle"].GetDouble() * M_PI / 180; 320 | jointDef->lowerTranslation = jsonJoint["lowerTranslation"].GetDouble() * PTM_RATIO; 321 | jointDef->upperTranslation = jsonJoint["upperTranslation"].GetDouble() * PTM_RATIO; 322 | jointDef->enableMotor = jsonJoint["enableMotor"].GetBool(); 323 | jointDef->motorSpeed = -jsonJoint["motorSpeed"].GetDouble(); 324 | 325 | b2PrismaticJoint *joint = (b2PrismaticJoint*)world->CreateJoint(jointDef); 326 | 327 | std::string jsonUserData = jsonJoint["userData"].GetString(); 328 | char *userData = new char[strlen(jsonUserData.c_str()) + 1]; 329 | strcpy(userData, jsonUserData.c_str()); 330 | joint->SetUserData(userData); 331 | loadedJoints.push_back(joint); 332 | } 333 | else if (jointType == JOINT_ROPE){ 334 | b2RopeJointDef *jointDef = new b2RopeJointDef(); 335 | jointDef->bodyA = loadedBodies.at(jsonJoint["bodyA"].GetInt()); 336 | jointDef->bodyB = loadedBodies.at(jsonJoint["bodyB"].GetInt()); 337 | rapidjson::Value &localAnchorA = jsonJoint["localAnchorA"]; 338 | rapidjson::Value &localAnchorB = jsonJoint["localAnchorB"]; 339 | jointDef->localAnchorA.Set(localAnchorA[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorA[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 340 | jointDef->localAnchorB.Set(localAnchorB[rapidjson::SizeType(0)].GetDouble() / PTM_RATIO, -localAnchorB[rapidjson::SizeType(1)].GetDouble() / PTM_RATIO); 341 | jointDef->collideConnected = jsonJoint["collideConnected"].GetBool(); 342 | jointDef->maxLength = jsonJoint["maxLength"].GetDouble() / PTM_RATIO; 343 | 344 | b2RopeJoint *joint = (b2RopeJoint*)world->CreateJoint(jointDef); 345 | 346 | std::string jsonUserData = jsonJoint["userData"].GetString(); 347 | char *userData = new char[strlen(jsonUserData.c_str()) + 1]; 348 | strcpy(userData, jsonUserData.c_str()); 349 | joint->SetUserData(userData); 350 | loadedJoints.push_back(joint); 351 | } 352 | } 353 | } 354 | 355 | std::vector Box2dWorldLoader::getLoadedBodies(){ 356 | return loadedBodies; 357 | } 358 | 359 | std::vector Box2dWorldLoader::getLoadedJoints(){ 360 | return loadedJoints; 361 | } -------------------------------------------------------------------------------- /resources/loaders/Cocos2d-x/Box2dWorldLoader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Author: Amit Kumar Mehar 5 | 6 | This software is provided 'as-is', without any express or implied 7 | warranty. In no event will the authors be held liable for any damages 8 | arising from the use of this software. 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would be 15 | appreciated but is not required. 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 3. This notice may not be removed or altered from any source distribution. 19 | */ 20 | 21 | #include // for file loading only 22 | #include // for creating physics objects 23 | #include 24 | #include // for parsing json file 25 | 26 | #define PTM_RATIO 30.0 // pixel to meter ratio 27 | 28 | struct b2BodyUserData 29 | { 30 | std::string userData; 31 | std::string textureData; 32 | 33 | b2BodyUserData(const char *data, const char *texData) : userData(""), textureData(""){ 34 | userData.append(data); 35 | textureData.append(texData); 36 | } 37 | }; 38 | 39 | class Box2dWorldLoader 40 | { 41 | private: 42 | // shape types used by the parser 43 | enum ShapeTypes{ 44 | SHAPE_BOX, 45 | SHAPE_CIRCLE, 46 | SHAPE_POLYGON, 47 | SHAPE_CHAIN 48 | }; 49 | // joint type used by the parser 50 | enum JointTypes { 51 | JOINT_DISTANCE, 52 | JOINT_WELD, 53 | JOINT_REVOLUTE, 54 | JOINT_WHEEL, 55 | JOINT_PULLEY, 56 | JOINT_GEAR, 57 | JOINT_PRISMATIC, 58 | JOINT_ROPE 59 | }; 60 | 61 | // to offset world's position (in pixels) 62 | float offsetX, offsetY; 63 | 64 | // vector to store loaded bodies, necessary to load joints (keep track of bodyA and bodyB, as used by joints) 65 | std::vector loadedBodies; 66 | // vector to store loaded joints, necessary to create gear joints (keep track of joint1 and joint2, as used by it) 67 | std::vector loadedJoints; 68 | 69 | // reset the object to load new scene 70 | void reset(){ 71 | offsetX = offsetY = 0; 72 | 73 | if (loadedBodies.size() > 0){ 74 | loadedBodies.clear(); 75 | } 76 | if (loadedJoints.size() > 0){ 77 | loadedJoints.clear(); 78 | } 79 | } 80 | 81 | // to load bodies 82 | void loadBodies(rapidjson::Value &, b2World *); 83 | // to load fixtures 84 | std::vector loadJsonFixture(rapidjson::Value &); 85 | // to load joints 86 | void loadJoints(rapidjson::Value &, b2World *); 87 | public: 88 | Box2dWorldLoader(); 89 | ~Box2dWorldLoader(); 90 | void loadJsonScene(const char*, b2World *); 91 | void setOffset(float, float); 92 | std::vector getLoadedBodies(); 93 | std::vector getLoadedJoints(); 94 | }; 95 | 96 | -------------------------------------------------------------------------------- /resources/loaders/Cocos2d-x/b2DebugDraw.cpp: -------------------------------------------------------------------------------- 1 | #include "b2DebugDraw.h" 2 | 3 | 4 | b2DebugDraw::b2DebugDraw(float ratio) 5 | { 6 | this->RATIO = ratio; 7 | } 8 | 9 | void b2DebugDraw::DrawPolygon(const b2Vec2* vertices, int vertexCount, const b2Color& color){ 10 | glLineWidth(2); 11 | 12 | Vec2 *verts = new Vec2[vertexCount]; 13 | for (int i = 0; i < vertexCount; i++){ 14 | verts[i] = Vec2(vertices[i].x * RATIO, vertices[i].y * RATIO); 15 | } 16 | DrawPrimitives::setDrawColor4F(color.r, color.g, color.b, 1); 17 | DrawPrimitives::drawPoly(verts, vertexCount, true); 18 | 19 | glLineWidth(1); 20 | } 21 | 22 | void b2DebugDraw::DrawSolidPolygon(const b2Vec2* vertices, int vertexCount, const b2Color& color){ 23 | glLineWidth(2); 24 | DrawPrimitives::setDrawColor4F(color.r, color.g, color.b, 1); 25 | 26 | Vec2 *verts = new Vec2[vertexCount]; 27 | Vec2 *prevVert; 28 | for (int i = 0; i < vertexCount; i++){ 29 | verts[i] = Vec2(vertices[i].x * RATIO, vertices[i].y * RATIO); 30 | 31 | // render edges 32 | if (i != 0){ 33 | DrawPrimitives::drawLine(*prevVert, verts[i]); 34 | } 35 | 36 | if (i == vertexCount - 1){ 37 | DrawPrimitives::drawLine(verts[i], verts[0]); 38 | } 39 | prevVert = &verts[i]; 40 | } 41 | 42 | glLineWidth(2); 43 | 44 | // enable blending 45 | glEnable(GL_BLEND); 46 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 47 | 48 | // render solid shape with transparency 49 | DrawPrimitives::drawSolidPoly(verts, vertexCount, Color4F(color.r, color.g, color.b, 0.5)); 50 | 51 | // disalbe blending 52 | glDisable(GL_BLEND); 53 | } 54 | 55 | void b2DebugDraw::DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color){ 56 | glLineWidth(2); 57 | 58 | DrawPrimitives::setDrawColor4F(color.r, color.g, color.b, 1); 59 | DrawPrimitives::drawCircle(Vec2(center.x * RATIO, center.y * RATIO), radius * RATIO, 360, 20, true); 60 | 61 | glLineWidth(1); 62 | } 63 | 64 | void b2DebugDraw::DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color){ 65 | // enable blending 66 | glEnable(GL_BLEND); 67 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 68 | 69 | DrawPrimitives::setDrawColor4F(color.r, color.g, color.b, 0.5); 70 | DrawPrimitives::drawSolidCircle(Vec2(center.x * RATIO, center.y * RATIO), radius * RATIO, 360, 20); 71 | 72 | // disalbe blending 73 | glDisable(GL_BLEND); 74 | 75 | DrawCircle(center, radius, color); 76 | } 77 | 78 | void b2DebugDraw::DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color){ 79 | glLineWidth(2); 80 | 81 | DrawPrimitives::setDrawColor4F(color.r, color.g, color.b, 1); 82 | DrawPrimitives::drawLine(Vec2(p1.x * RATIO, p1.y * RATIO), Vec2(p2.x * RATIO, p2.y * RATIO)); 83 | 84 | glLineWidth(1); 85 | } 86 | 87 | void b2DebugDraw::DrawTransform(const b2Transform& xf){ 88 | Vec2 pos = Vec2(xf.p.x * RATIO, xf.p.y * RATIO), dest; 89 | float length = 20; 90 | 91 | glLineWidth(2); 92 | 93 | // render x-axis 94 | DrawPrimitives::setDrawColor4F(1, 0, 0, 1); 95 | dest = Vec2(pos.x + length * xf.q.GetXAxis().x, pos.y + length * xf.q.GetXAxis().y); 96 | DrawPrimitives::drawLine(pos, dest); 97 | 98 | // render y-axis 99 | DrawPrimitives::setDrawColor4F(0, 1, 0, 1); 100 | dest = Vec2(pos.x + length * xf.q.GetYAxis().x, pos.y + length * xf.q.GetYAxis().y); 101 | DrawPrimitives::drawLine(pos, dest); 102 | 103 | glLineWidth(1); 104 | } 105 | 106 | b2DebugDraw::~b2DebugDraw() 107 | { 108 | } 109 | -------------------------------------------------------------------------------- /resources/loaders/Cocos2d-x/b2DebugDraw.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Author: Amit Kumar Mehar 5 | 6 | This software is provided 'as-is', without any express or implied 7 | warranty. In no event will the authors be held liable for any damages 8 | arising from the use of this software. 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would be 15 | appreciated but is not required. 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 3. This notice may not be removed or altered from any source distribution. 19 | */ 20 | 21 | #include 22 | #include 23 | 24 | USING_NS_CC; 25 | 26 | class b2DebugDraw : public b2Draw 27 | { 28 | private: 29 | // pixel to meter ratio 30 | float RATIO; 31 | public: 32 | b2DebugDraw(float); 33 | ~b2DebugDraw(); 34 | 35 | virtual void DrawPolygon(const b2Vec2* vertices, int vertexCount, const b2Color& color); 36 | 37 | virtual void DrawSolidPolygon(const b2Vec2* vertices, int vertexCount, const b2Color& color); 38 | 39 | virtual void DrawCircle(const b2Vec2& center, float32 radius, const b2Color& color); 40 | 41 | virtual void DrawSolidCircle(const b2Vec2& center, float32 radius, const b2Vec2& axis, const b2Color& color); 42 | 43 | virtual void DrawSegment(const b2Vec2& p1, const b2Vec2& p2, const b2Color& color); 44 | 45 | virtual void DrawTransform(const b2Transform& xf); 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /resources/loaders/Cocos2d-x/b2DebugLayer.cpp: -------------------------------------------------------------------------------- 1 | #include "b2DebugLayer.h" 2 | 3 | 4 | b2DebugLayer* b2DebugLayer::create(b2World* pB2World, float ratio){ 5 | b2DebugLayer *layer = new b2DebugLayer(pB2World, ratio); 6 | if (layer && layer->init()) 7 | { 8 | layer->autorelease(); 9 | return layer; 10 | } 11 | else 12 | { 13 | delete layer; 14 | layer = NULL; 15 | return NULL; 16 | } 17 | } 18 | 19 | b2DebugLayer::b2DebugLayer(b2World *world, float ratio){ 20 | this->world = world; 21 | this->RATIO = ratio; 22 | } 23 | 24 | bool b2DebugLayer::init(){ 25 | if (!Layer::init()){ 26 | return false; 27 | } 28 | 29 | debugDraw = new b2DebugDraw(RATIO); 30 | 31 | uint32 flags = 0; 32 | flags += b2Draw::e_shapeBit; // to render shape 33 | flags += b2Draw::e_jointBit; // to render joints 34 | //flags += b2Draw::e_aabbBit; // to render aabb (bounding box of physics body) 35 | //flags += b2Draw::e_pairBit; 36 | flags += b2Draw::e_centerOfMassBit; // to render transform (local x and y axis) 37 | debugDraw->SetFlags(flags); 38 | 39 | world->SetDebugDraw(debugDraw); 40 | 41 | return true; 42 | } 43 | 44 | void b2DebugLayer::setDebugFlags(uint32 flags){ 45 | debugDraw->SetFlags(flags); 46 | } 47 | 48 | b2DebugLayer::~b2DebugLayer() 49 | { 50 | } 51 | -------------------------------------------------------------------------------- /resources/loaders/Cocos2d-x/b2DebugLayer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | Author: Amit Kumar Mehar 5 | 6 | This software is provided 'as-is', without any express or implied 7 | warranty. In no event will the authors be held liable for any damages 8 | arising from the use of this software. 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would be 15 | appreciated but is not required. 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 3. This notice may not be removed or altered from any source distribution. 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | USING_NS_CC; 26 | 27 | class b2DebugLayer : public Layer 28 | { 29 | private: 30 | b2World *world; 31 | float RATIO = 30.0; // pixel to meter ratio 32 | b2DebugDraw *debugDraw; // handles rendering (polygons, segments, transforms) 33 | 34 | public: 35 | // always use b2DebugLayer::create to create instances of debug layer 36 | static b2DebugLayer* create(b2World *, float); 37 | b2DebugLayer(b2World *, float); 38 | ~b2DebugLayer(); 39 | 40 | virtual bool init(); 41 | void setDebugFlags(uint32); 42 | 43 | // overriding draw function (is different than previous versions of coocs2d) 44 | CustomCommand _command; 45 | virtual void draw(Renderer *renderer, const Mat4& transform, uint32_t flags){ 46 | _command.init(_globalZOrder); 47 | _command.func = CC_CALLBACK_0(b2DebugLayer::OnDraw, this, transform, flags); 48 | renderer->addCommand(&_command); 49 | } 50 | void OnDraw(const Mat4& transform, uint32_t flags){ 51 | kmGLPushMatrix(); 52 | kmGLLoadMatrix(&transform); 53 | world->DrawDebugData(); 54 | kmGLPopMatrix(); 55 | } 56 | }; 57 | 58 | -------------------------------------------------------------------------------- /resources/loaders/LibGdx/WorldLoader.java: -------------------------------------------------------------------------------- 1 | package physics; 2 | 3 | /* 4 | Author: Amit Kumar Mehar 5 | 6 | This software is provided 'as-is', without any express or implied 7 | warranty. In no event will the authors be held liable for any damages 8 | arising from the use of this software. 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 1. The origin of this software must not be misrepresented; you must not 13 | claim that you wrote the original software. If you use this software 14 | in a product, an acknowledgment in the product documentation would be 15 | appreciated but is not required. 16 | 2. Altered source versions must be plainly marked as such, and must not be 17 | misrepresented as being the original software. 18 | 3. This notice may not be removed or altered from any source distribution. 19 | */ 20 | 21 | import java.io.BufferedReader; 22 | import java.io.IOException; 23 | import java.util.ArrayList; 24 | 25 | import com.amu.src.json.JSONArray; 26 | import com.amu.src.json.JSONObject; 27 | import com.badlogic.gdx.Gdx; 28 | import com.badlogic.gdx.files.FileHandle; 29 | import com.badlogic.gdx.math.Vector2; 30 | import com.badlogic.gdx.physics.box2d.Body; 31 | import com.badlogic.gdx.physics.box2d.BodyDef; 32 | import com.badlogic.gdx.physics.box2d.BodyDef.BodyType; 33 | import com.badlogic.gdx.physics.box2d.ChainShape; 34 | import com.badlogic.gdx.physics.box2d.CircleShape; 35 | import com.badlogic.gdx.physics.box2d.FixtureDef; 36 | import com.badlogic.gdx.physics.box2d.Joint; 37 | import com.badlogic.gdx.physics.box2d.PolygonShape; 38 | import com.badlogic.gdx.physics.box2d.World; 39 | import com.badlogic.gdx.physics.box2d.joints.DistanceJoint; 40 | import com.badlogic.gdx.physics.box2d.joints.DistanceJointDef; 41 | import com.badlogic.gdx.physics.box2d.joints.GearJoint; 42 | import com.badlogic.gdx.physics.box2d.joints.GearJointDef; 43 | import com.badlogic.gdx.physics.box2d.joints.PrismaticJoint; 44 | import com.badlogic.gdx.physics.box2d.joints.PrismaticJointDef; 45 | import com.badlogic.gdx.physics.box2d.joints.PulleyJoint; 46 | import com.badlogic.gdx.physics.box2d.joints.PulleyJointDef; 47 | import com.badlogic.gdx.physics.box2d.joints.RevoluteJoint; 48 | import com.badlogic.gdx.physics.box2d.joints.RevoluteJointDef; 49 | import com.badlogic.gdx.physics.box2d.joints.RopeJoint; 50 | import com.badlogic.gdx.physics.box2d.joints.RopeJointDef; 51 | import com.badlogic.gdx.physics.box2d.joints.WeldJoint; 52 | import com.badlogic.gdx.physics.box2d.joints.WeldJointDef; 53 | import com.badlogic.gdx.physics.box2d.joints.WheelJoint; 54 | import com.badlogic.gdx.physics.box2d.joints.WheelJointDef; 55 | 56 | /* 57 | Json library can be downloaded from : https://github.com/douglascrockford/JSON-java 58 | */ 59 | 60 | public class WorldLoader { 61 | /** meter to pixel ratio */ 62 | public static float RATIO = 30; 63 | 64 | private BodyType bodyTypes[] = { 65 | BodyType.StaticBody, 66 | BodyType.KinematicBody, 67 | BodyType.DynamicBody 68 | }; 69 | private enum ShapeTypes { 70 | SHAPE_BOX, 71 | SHAPE_CIRCLE, 72 | SHAPE_POLYGON, 73 | SHAPE_CHAIN 74 | }; 75 | private enum JointTypes { 76 | JOINT_DISTANCE, 77 | JOINT_WELD, 78 | JOINT_REVOLUTE, 79 | JOINT_WHEEL, 80 | JOINT_PULLEY, 81 | JOINT_GEAR, 82 | JOINT_PRISMATIC, 83 | JOINT_ROPE 84 | }; 85 | 86 | /** array list of all the bodies loaded to use when creating joints **/ 87 | public ArrayList loadedBodies; 88 | 89 | /** array list of all the joints loaded to use when creating gear joints **/ 90 | private ArrayList loadedJoints; 91 | 92 | /** to offset scene's position (in pixels) **/ 93 | public float offsetX = 0, offsetY = 0; 94 | 95 | /** ready the loader to load new scene **/ 96 | private void reset(){ 97 | if (loadedBodies != null){ 98 | loadedBodies.clear(); 99 | loadedBodies = null; 100 | } 101 | loadedBodies = new ArrayList(); 102 | if (loadedJoints != null){ 103 | loadedJoints.clear(); 104 | loadedJoints = null; 105 | } 106 | loadedJoints = new ArrayList(); 107 | } 108 | 109 | /** 110 | * @param data 111 | * path to json file 112 | * @param world 113 | * world to which bodies will be added 114 | * @throws 115 | * throws IOException if file is not available 116 | */ 117 | public void loadJsonScene(String data, World world) throws IOException{ 118 | // reset loader 119 | reset(); 120 | 121 | FileHandle handle = Gdx.files.internal(data); 122 | BufferedReader reader = handle.reader(255); 123 | String line = null; 124 | StringBuilder output = new StringBuilder(); 125 | while ((line = reader.readLine()) != null){ 126 | output.append(line); 127 | } 128 | String jsonData = output.toString(); 129 | 130 | JSONObject scene = new JSONObject(jsonData); 131 | JSONArray bodies = scene.getJSONArray("bodies"); 132 | for (int i = 0; i < bodies.length(); i++){ 133 | JSONObject jsonBody = bodies.getJSONObject(i); 134 | BodyDef bodyDef = new BodyDef(); 135 | bodyDef.type = bodyTypes[jsonBody.getInt("type")]; 136 | Body body = world.createBody(bodyDef); 137 | body.setBullet(jsonBody.getBoolean("isBullet")); 138 | body.setUserData(jsonBody.getString("userData")); 139 | body.setTransform ( 140 | (float) (offsetX / RATIO + jsonBody .getJSONArray("position").getDouble(0) / RATIO), 141 | (float) (offsetY / RATIO - jsonBody .getJSONArray("position").getDouble(1) / RATIO), 142 | (float) (-jsonBody.getDouble("rotation") * Math.PI / 180) 143 | ); 144 | body.setFixedRotation(jsonBody.getBoolean("isFixedRotation")); 145 | body.setLinearDamping((float) jsonBody.getDouble("linearDamping")); 146 | body.setAngularDamping((float) jsonBody.getDouble("angularDamping")); 147 | 148 | // add body to array list to use when creating joints 149 | loadedBodies.add(body); 150 | 151 | ArrayList fixtures = loadJsonFixture(jsonBody .getJSONArray("fixtures")); 152 | for (int j = 0; j < fixtures.size(); j++){ 153 | body.createFixture(fixtures.get(j)); 154 | } 155 | } 156 | 157 | JSONArray joints = scene.getJSONArray("joints"); 158 | createJoints(joints, world); 159 | } 160 | 161 | /** 162 | * 163 | * @param jsonFixtures 164 | * json array containing fixture information 165 | * @return 166 | * returns array list of box2d fixture definition 167 | */ 168 | private ArrayList loadJsonFixture(JSONArray jsonFixtures){ 169 | ArrayList fixtures = new ArrayList(); 170 | 171 | for (int i = 0; i < jsonFixtures.length(); i++){ 172 | JSONObject jsonFixture = jsonFixtures.getJSONObject(i); 173 | float density = (float) jsonFixture.getDouble("density"), 174 | restitution = (float) jsonFixture.getDouble("restitution"), 175 | friction = (float) jsonFixture.getDouble("friction"); 176 | short maskBits = (short) jsonFixture.getDouble("maskBits"), 177 | categoryBits = (short) jsonFixture.getDouble("categoryBits"), 178 | groupIndex = (short) jsonFixture.getDouble("groupIndex"); 179 | boolean isSensor = jsonFixture.getBoolean("isSensor"); 180 | 181 | for (int k = 0; k < jsonFixture.getJSONArray("shapes").length(); k++){ 182 | JSONObject jsonShape = jsonFixture.getJSONArray("shapes").getJSONObject(k); 183 | 184 | FixtureDef fixture = new FixtureDef(); 185 | fixture.density = density; 186 | fixture.friction = friction; 187 | fixture.restitution = restitution; 188 | fixture.isSensor = isSensor; 189 | fixture.filter.maskBits = maskBits; 190 | fixture.filter.categoryBits = categoryBits; 191 | fixture.filter.groupIndex = groupIndex; 192 | 193 | Vector2 position = new Vector2(jsonShape.getJSONArray("position").getInt(0) / RATIO, 194 | -jsonShape.getJSONArray("position").getInt(1) / RATIO); 195 | 196 | if (jsonShape.getInt("type") == ShapeTypes.SHAPE_BOX.ordinal()){ 197 | PolygonShape shape = new PolygonShape(); 198 | shape.setAsBox((float)jsonShape.getDouble("width") / (2 * RATIO), (float)jsonShape.getDouble("height") / (2 * RATIO), position, 0); 199 | fixture.shape = shape; 200 | } 201 | else if (jsonShape.getInt("type") == ShapeTypes.SHAPE_CIRCLE.ordinal()){ 202 | CircleShape shape = new CircleShape(); 203 | shape.setRadius((float) jsonShape.getDouble("radius") * 2 / RATIO); 204 | shape.setPosition(position); 205 | fixture.shape = shape; 206 | } 207 | else if (jsonShape.getInt("type") == ShapeTypes.SHAPE_POLYGON.ordinal()){ 208 | PolygonShape shape = new PolygonShape(); 209 | JSONArray jsonVertices = jsonShape.getJSONArray("vertices"); 210 | Vector2 vertices[] = new Vector2[jsonVertices.length()]; 211 | for (int j = 0; j < jsonVertices.length(); j++){ 212 | Vector2 vertex = new Vector2(position.x + jsonVertices.getJSONArray(j).getInt(0) / RATIO, 213 | position.y - jsonVertices.getJSONArray(j).getInt(1) / RATIO); 214 | vertices[j] = vertex; 215 | } 216 | shape.set(vertices); 217 | fixture.shape = shape; 218 | } 219 | else if (jsonShape.getInt("type") == ShapeTypes.SHAPE_CHAIN.ordinal()){ 220 | ChainShape shape = new ChainShape(); 221 | JSONArray jsonVertices = jsonShape.getJSONArray("vertices"); 222 | Vector2 vertices[] = new Vector2[jsonVertices.length()]; 223 | for (int j = 0; j < jsonVertices.length(); j++){ 224 | Vector2 vertex = new Vector2(position.x + jsonVertices.getJSONArray(j).getInt(0) / RATIO, 225 | position.y - jsonVertices.getJSONArray(j).getInt(1) / RATIO); 226 | vertices[j] = vertex; 227 | } 228 | shape.createChain(vertices); 229 | fixture.shape = shape; 230 | } 231 | fixtures.add(fixture); 232 | } 233 | } 234 | return fixtures; 235 | } 236 | 237 | /** 238 | * 239 | * @param jsonJoints 240 | * json array containing joints information 241 | * @param world 242 | * world in which joints will be created 243 | */ 244 | private void createJoints(JSONArray jsonJoints, World world){ 245 | for (int i = 0; i < jsonJoints.length(); i++){ 246 | JSONObject jsonJoint = jsonJoints.getJSONObject(i); 247 | 248 | if (jsonJoint.getInt("jointType") == JointTypes.JOINT_DISTANCE.ordinal()){ 249 | DistanceJointDef jointDef = new DistanceJointDef(); 250 | jointDef.bodyA = loadedBodies.get(jsonJoint.getInt("bodyA")); 251 | jointDef.bodyB = loadedBodies.get(jsonJoint.getInt("bodyB")); 252 | jointDef.localAnchorA.set(new Vector2(jsonJoint.getJSONArray("localAnchorA").getInt(0) / RATIO, 253 | -jsonJoint.getJSONArray("localAnchorA").getInt(1) / RATIO)); 254 | jointDef.localAnchorB.set(new Vector2(jsonJoint.getJSONArray("localAnchorB").getInt(0) / RATIO, 255 | -jsonJoint.getJSONArray("localAnchorB").getInt(1) / RATIO)); 256 | jointDef.collideConnected = jsonJoint.getBoolean("collideConnected"); 257 | jointDef.length = (float)jsonJoint.getDouble("length") / 30; 258 | jointDef.dampingRatio = (float)jsonJoint.getDouble("dampingRatio"); 259 | jointDef.frequencyHz = (float)jsonJoint.getDouble("frequencyHZ"); 260 | 261 | DistanceJoint joint = (DistanceJoint) world.createJoint(jointDef); 262 | joint.setUserData(jsonJoint.getString("userData")); 263 | loadedJoints.add(joint); 264 | } 265 | else if (jsonJoint.getInt("jointType") == JointTypes.JOINT_WELD.ordinal()){ 266 | WeldJointDef jointDef = new WeldJointDef(); 267 | jointDef.bodyA = loadedBodies.get(jsonJoint.getInt("bodyA")); 268 | jointDef.bodyB = loadedBodies.get(jsonJoint.getInt("bodyB")); 269 | jointDef.localAnchorA.set(new Vector2(jsonJoint.getJSONArray("localAnchorA").getInt(0) / RATIO, 270 | -jsonJoint.getJSONArray("localAnchorA").getInt(1) / RATIO)); 271 | jointDef.localAnchorB.set(new Vector2(jsonJoint.getJSONArray("localAnchorB").getInt(0) / RATIO, 272 | -jsonJoint.getJSONArray("localAnchorB").getInt(1) / RATIO)); 273 | jointDef.collideConnected = jsonJoint.getBoolean("collideConnected"); 274 | 275 | jointDef.referenceAngle = (float) (-jsonJoint.getDouble("referenceAngle") * Math.PI / 180); 276 | 277 | WeldJoint joint = (WeldJoint) world.createJoint(jointDef); 278 | joint.setUserData(jsonJoint.getString("userData")); 279 | loadedJoints.add(joint); 280 | } 281 | else if (jsonJoint.getInt("jointType") == JointTypes.JOINT_REVOLUTE.ordinal()){ 282 | RevoluteJointDef jointDef = new RevoluteJointDef(); 283 | jointDef.bodyA = loadedBodies.get(jsonJoint.getInt("bodyA")); 284 | jointDef.bodyB = loadedBodies.get(jsonJoint.getInt("bodyB")); 285 | jointDef.localAnchorA.set(new Vector2(jsonJoint.getJSONArray("localAnchorA").getInt(0) / RATIO, 286 | -jsonJoint.getJSONArray("localAnchorA").getInt(1) / RATIO)); 287 | jointDef.localAnchorB.set(new Vector2(jsonJoint.getJSONArray("localAnchorB").getInt(0) / RATIO, 288 | -jsonJoint.getJSONArray("localAnchorB").getInt(1) / RATIO)); 289 | 290 | jointDef.collideConnected = jsonJoint.getBoolean("collideConnected");; 291 | jointDef.enableLimit = jsonJoint.getBoolean("enableLimit"); 292 | jointDef.enableMotor = jsonJoint.getBoolean("enableMotor"); 293 | jointDef.upperAngle = (float) -(jsonJoint.getDouble("lowerAngle") * Math.PI / 180); // because of y-axis flipping 294 | jointDef.maxMotorTorque = (float)jsonJoint.getDouble("maxMotorTorque"); 295 | jointDef.motorSpeed = (float)-jsonJoint.getDouble("motorSpeed"); 296 | jointDef.referenceAngle = (float) -(jsonJoint.getDouble("referenceAngle") * Math.PI / 180); 297 | jointDef.lowerAngle = (float) -(jsonJoint.getDouble("upperAngle") * Math.PI / 180); // because of y-axis flipping 298 | 299 | RevoluteJoint joint = (RevoluteJoint) world.createJoint(jointDef); 300 | joint.setUserData(jsonJoint.getString("userData")); 301 | loadedJoints.add(joint); 302 | } 303 | else if (jsonJoint.getInt("jointType") == JointTypes.JOINT_WHEEL.ordinal()){ 304 | WheelJointDef jointDef = new WheelJointDef(); 305 | jointDef.bodyA = loadedBodies.get(jsonJoint.getInt("bodyA")); 306 | jointDef.bodyB = loadedBodies.get(jsonJoint.getInt("bodyB")); 307 | jointDef.localAnchorA.set(new Vector2(jsonJoint.getJSONArray("localAnchorA").getInt(0) / RATIO, 308 | -jsonJoint.getJSONArray("localAnchorA").getInt(1) / RATIO)); 309 | jointDef.localAnchorB.set(new Vector2(jsonJoint.getJSONArray("localAnchorB").getInt(0) / RATIO, 310 | -jsonJoint.getJSONArray("localAnchorB").getInt(1) / RATIO)); 311 | 312 | jointDef.localAxisA.set(new Vector2((float)jsonJoint.getJSONArray("localAxisA").getDouble(0), (float)-jsonJoint.getJSONArray("localAxisA").getDouble(1))); 313 | 314 | jointDef.collideConnected = jsonJoint.getBoolean("collideConnected");; 315 | jointDef.enableMotor = jsonJoint.getBoolean("enableMotor"); 316 | jointDef.maxMotorTorque = (float)jsonJoint.getDouble("maxMotorTorque"); 317 | jointDef.motorSpeed = (float)-jsonJoint.getDouble("motorSpeed"); 318 | jointDef.dampingRatio = (float)jsonJoint.getDouble("dampingRatio"); 319 | jointDef.frequencyHz = (float)jsonJoint.getDouble("frequencyHZ"); 320 | 321 | WheelJoint joint = (WheelJoint) world.createJoint(jointDef); 322 | joint.setUserData(jsonJoint.getString("userData")); 323 | loadedJoints.add(joint); 324 | } 325 | else if (jsonJoint.getInt("jointType") == JointTypes.JOINT_PULLEY.ordinal()){ 326 | PulleyJointDef jointDef = new PulleyJointDef(); 327 | jointDef.bodyA = loadedBodies.get(jsonJoint.getInt("bodyA")); 328 | jointDef.bodyB = loadedBodies.get(jsonJoint.getInt("bodyB")); 329 | jointDef.collideConnected = jsonJoint.getBoolean("collideConnected"); 330 | jointDef.localAnchorA.set(new Vector2(jsonJoint.getJSONArray("localAnchorA").getInt(0) / RATIO, 331 | -jsonJoint.getJSONArray("localAnchorA").getInt(1) / RATIO)); 332 | jointDef.localAnchorB.set(new Vector2(jsonJoint.getJSONArray("localAnchorB").getInt(0) / RATIO, 333 | -jsonJoint.getJSONArray("localAnchorB").getInt(1) / RATIO)); 334 | jointDef.groundAnchorA.set(new Vector2(offsetX / RATIO + jsonJoint.getJSONArray("groundAnchorA").getInt(0) / RATIO, 335 | offsetY / RATIO - jsonJoint.getJSONArray("groundAnchorA").getInt(1) / RATIO)); 336 | jointDef.groundAnchorB.set(new Vector2(offsetX / RATIO + jsonJoint.getJSONArray("groundAnchorB").getInt(0) / RATIO, 337 | offsetY / RATIO - jsonJoint.getJSONArray("groundAnchorB").getInt(1) / RATIO)); 338 | jointDef.lengthA = (float)jsonJoint.getDouble("maxLengthA") / RATIO; 339 | jointDef.lengthB = (float)jsonJoint.getDouble("maxLengthB") / RATIO; 340 | jointDef.ratio = (float)jsonJoint.getDouble("ratio"); 341 | 342 | PulleyJoint joint = (PulleyJoint) world.createJoint(jointDef); 343 | joint.setUserData(jsonJoint.getString("userData")); 344 | loadedJoints.add(joint); 345 | } 346 | else if (jsonJoint.getInt("jointType") == JointTypes.JOINT_GEAR.ordinal()){ 347 | GearJointDef jointDef = new GearJointDef(); 348 | jointDef.bodyA = loadedBodies.get(jsonJoint.getInt("bodyA")); 349 | jointDef.bodyB = loadedBodies.get(jsonJoint.getInt("bodyB")); 350 | jointDef.collideConnected = jsonJoint.getBoolean("collideConnected"); 351 | jointDef.ratio = (float)jsonJoint.getDouble("ratio"); 352 | 353 | jointDef.joint1 = loadedJoints.get(jsonJoint.getInt("joint1")); 354 | jointDef.joint2 = loadedJoints.get(jsonJoint.getInt("joint2")); 355 | 356 | GearJoint joint = (GearJoint) world.createJoint(jointDef); 357 | joint.setUserData(jsonJoint.getString("userData")); 358 | loadedJoints.add(joint); 359 | } 360 | else if (jsonJoint.getInt("jointType") == JointTypes.JOINT_PRISMATIC.ordinal()){ 361 | PrismaticJointDef jointDef = new PrismaticJointDef(); 362 | jointDef.bodyA = loadedBodies.get(jsonJoint.getInt("bodyA")); 363 | jointDef.bodyB = loadedBodies.get(jsonJoint.getInt("bodyB")); 364 | jointDef.localAnchorA.set(new Vector2(jsonJoint.getJSONArray("localAnchorA").getInt(0) / RATIO, 365 | -jsonJoint.getJSONArray("localAnchorA").getInt(1) / RATIO)); 366 | jointDef.localAnchorB.set(new Vector2(jsonJoint.getJSONArray("localAnchorB").getInt(0) / RATIO, 367 | -jsonJoint.getJSONArray("localAnchorB").getInt(1) / RATIO)); 368 | 369 | jointDef.localAxisA.set(new Vector2((float)jsonJoint.getJSONArray("localAxisA").getDouble(0), (float)-jsonJoint.getJSONArray("localAxisA").getDouble(1))); 370 | 371 | jointDef.collideConnected = jsonJoint.getBoolean("collideConnected");; 372 | jointDef.enableLimit = jsonJoint.getBoolean("enableLimit"); 373 | jointDef.enableMotor = jsonJoint.getBoolean("enableMotor"); 374 | jointDef.lowerTranslation = (float) (jsonJoint.getDouble("lowerTranslation") / RATIO); 375 | jointDef.maxMotorForce = (float)jsonJoint.getDouble("maxMotorForce"); 376 | jointDef.motorSpeed = (float)jsonJoint.getDouble("motorSpeed"); 377 | jointDef.referenceAngle = (float) (-jsonJoint.getDouble("referenceAngle") * Math.PI / 180); 378 | jointDef.upperTranslation = (float) (jsonJoint.getDouble("upperTranslation") / RATIO);; 379 | 380 | PrismaticJoint joint = (PrismaticJoint) world.createJoint(jointDef); 381 | joint.setUserData(jsonJoint.getString("userData")); 382 | loadedJoints.add(joint); 383 | } 384 | else if (jsonJoint.getInt("jointType") == JointTypes.JOINT_ROPE.ordinal()){ 385 | RopeJointDef jointDef = new RopeJointDef(); 386 | jointDef.bodyA = loadedBodies.get(jsonJoint.getInt("bodyA")); 387 | jointDef.bodyB = loadedBodies.get(jsonJoint.getInt("bodyB")); 388 | jointDef.localAnchorA.set(new Vector2(jsonJoint.getJSONArray("localAnchorA").getInt(0) / RATIO, 389 | -jsonJoint.getJSONArray("localAnchorA").getInt(1) / RATIO)); 390 | jointDef.localAnchorB.set(new Vector2(jsonJoint.getJSONArray("localAnchorB").getInt(0) / RATIO, 391 | -jsonJoint.getJSONArray("localAnchorB").getInt(1) / RATIO)); 392 | 393 | jointDef.maxLength = (float) (jsonJoint.getDouble("maxLength") / RATIO); 394 | 395 | RopeJoint joint = (RopeJoint) world.createJoint(jointDef); 396 | joint.setUserData(jsonJoint.getString("userData")); 397 | loadedJoints.add(joint); 398 | } 399 | 400 | } 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /resources/loaders/Sprite Kit/WorldLoader.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Amit Kumar Mehar 3 | 4 | This software is provided 'as-is', without any express or implied 5 | warranty. In no event will the authors be held liable for any damages 6 | arising from the use of this software. 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 1. The origin of this software must not be misrepresented; you must not 11 | claim that you wrote the original software. If you use this software 12 | in a product, an acknowledgment in the product documentation would be 13 | appreciated but is not required. 14 | 2. Altered source versions must be plainly marked as such, and must not be 15 | misrepresented as being the original software. 16 | 3. This notice may not be removed or altered from any source distribution. 17 | */ 18 | 19 | #ifndef HelloKit_b2WorldLoader_h 20 | #define HelloKit_b2WorldLoader_h 21 | 22 | #import // for creating physics objects 23 | 24 | // jsonKit can be dowloaded from : https://github.com/johnezang/JSONKit 25 | #import "JSONKIT.h" // for parsing json scene 26 | 27 | enum ShapeTypes{ 28 | SHAPE_BOX, 29 | SHAPE_CIRCLE, 30 | SHAPE_POLYGON, 31 | SHAPE_CHAIN 32 | }; 33 | 34 | /* 35 | 36 | Joint loading is unpredictable as sprite kit does not support 37 | many features included in box2d like separate anchor for revolute 38 | joint(pin joint in sprite kit), enabling and disabling motor in pin joints, etc. 39 | 40 | */ 41 | enum JointTypes{ 42 | JOINT_DISTANCE, // created as spring joint 43 | JOINT_WELD, // created as fixed joint 44 | JOINT_REVOLUTE, // created as pin joint 45 | JOINT_WHEEL, // not supported by sprite kit 46 | JOINT_PULLEY, // not supported by sprite kit 47 | JOINT_GEAR, // not supported by sprite kit 48 | JOINT_PRISMATIC, // not supported by sprite kit 49 | JOINT_ROPE // created as limit joint 50 | }; 51 | 52 | @interface WorldLoader : NSObject 53 | 54 | // to offset the world's position 55 | @property float offsetX, offsetY; 56 | 57 | // loadedBodies contains all the bodies loaded (SKPhysicsBody*) 58 | // loadedJoints contains all the joints loaded (SKPhysicsJoints*) 59 | @property NSMutableArray *loadedBodies, *loadedJoints; 60 | 61 | +(id) init; 62 | // to reset loader for loading new scene, called everytime a new scene is loaded 63 | -(void) reset; 64 | // loads the physics scene present in file 65 | -(void) loadJsonSceneFromFile : (NSString*)file : (SKScene*)scene; 66 | // loads fixtures present in bodies, also initializes parent node's physicsBody 67 | -(void) loadJsonFixtures : (NSArray*)jsonFixtures : (SKNode*) parent; 68 | 69 | @end 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /resources/loaders/Sprite Kit/WorldLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // b2dWorldLoader.m 3 | // 4 | // Created by Amit Kumar Mehar on 19/07/15. 5 | // Copyright (c) 2015 Amit Kumar Mehar. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | #import "WorldLoader.h" 11 | 12 | @implementation WorldLoader 13 | 14 | @synthesize offsetX, offsetY; 15 | @synthesize loadedBodies, loadedJoints; 16 | 17 | +(id) init{ 18 | 19 | return self; 20 | } 21 | 22 | -(void) reset{ 23 | // release arrays 24 | if ([loadedBodies count] > 0){ 25 | [loadedBodies removeAllObjects]; 26 | loadedBodies = nil; 27 | } 28 | if ([loadedJoints count] > 0){ 29 | [loadedJoints removeAllObjects]; 30 | loadedJoints = nil; 31 | } 32 | 33 | // reinitialize array 34 | loadedBodies = [[NSMutableArray alloc] init]; 35 | loadedJoints = [[NSMutableArray alloc] init]; 36 | } 37 | 38 | -(void) loadJsonSceneFromFile:(NSString *)file : (SKScene*)scene{ 39 | [self reset]; 40 | 41 | // load the json file 42 | NSString *filePath = [[NSBundle mainBundle] pathForResource:file ofType:@"json"]; 43 | NSString *data = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:NULL]; 44 | 45 | // parse the loaded file to json object 46 | NSDictionary *jsonScene = [data objectFromJSONString]; 47 | 48 | // load physics bodies 49 | NSArray *jsonBodies = [jsonScene objectForKey:@"bodies"]; 50 | for (int i = 0, numberOfBodies = [jsonBodies count]; i < numberOfBodies; i++){ 51 | // load body's properties 52 | NSDictionary *jsonBody = [jsonBodies objectAtIndex:i]; 53 | NSArray *position = [jsonBody objectForKey:@"position"]; 54 | float rotation = -[[jsonBody objectForKey:@"rotation"] floatValue] * M_PI / 180.0, 55 | linearDamping = [[jsonBody objectForKey:@"linearDamping"] floatValue], 56 | angularDamping = [[jsonBody objectForKey:@"angularDamping"] floatValue]; 57 | bool isFixedRotation = [[jsonBody objectForKey:@"isFixedRotation"] boolValue]; 58 | int bodyType = [[jsonBody objectForKey:@"type"] integerValue]; 59 | NSString *userData = [jsonBody objectForKey:@"userData"]; 60 | 61 | // create node for body 62 | SKNode *body = [[SKNode alloc] init]; 63 | [body setName: userData]; 64 | [body setPosition:CGPointMake(offsetX + [[position objectAtIndex:0] floatValue], offsetY - [[position objectAtIndex:1] floatValue])]; 65 | [body setZRotation:rotation]; 66 | [scene addChild:body]; 67 | 68 | // load fixtures / shapes for the body 69 | NSArray *jsonFixtures = [jsonBody objectForKey:@"fixtures"]; 70 | // add fixtures to the body (also initializes physics body for current body node) 71 | [self loadJsonFixtures:jsonFixtures :body]; 72 | 73 | if (bodyType == 0){ 74 | [body.physicsBody setDynamic:false]; 75 | } 76 | // kinematic bodies are not supported in sprite kit 77 | else if (bodyType == 2){ 78 | [body.physicsBody setDynamic:true]; 79 | } 80 | [body.physicsBody setLinearDamping:linearDamping]; 81 | [body.physicsBody setAngularDamping:angularDamping]; 82 | [body.physicsBody setAllowsRotation:!isFixedRotation]; 83 | 84 | [loadedBodies addObject:body.physicsBody]; 85 | } 86 | 87 | // load physics joints 88 | NSArray *jsonJoints = [jsonScene objectForKey:@"joints"]; 89 | for (int i = 0, numberOfJoints = [jsonJoints count]; i < numberOfJoints; i++){ 90 | NSDictionary *jsonJoint = [jsonJoints objectAtIndex:i]; 91 | 92 | int jointType = [[jsonJoint objectForKey:@"jointType"] integerValue]; 93 | 94 | if (jointType == JOINT_DISTANCE){ 95 | int indexA = [[jsonJoint objectForKey:@"bodyA"] integerValue]; 96 | int indexB = [[jsonJoint objectForKey:@"bodyB"] integerValue]; 97 | SKPhysicsBody *bodyA = [loadedBodies objectAtIndex:indexA]; 98 | SKPhysicsBody *bodyB = [loadedBodies objectAtIndex:indexB]; 99 | CGPoint anchorA = CGPointMake([[[jsonJoint objectForKey:@"localAnchorA"] objectAtIndex:0] floatValue], -[[[jsonJoint objectForKey:@"localAnchorA"] objectAtIndex:1] floatValue]); 100 | anchorA.x += bodyA.node.position.x; 101 | anchorA.y += bodyA.node.position.y; 102 | CGPoint anchorB = CGPointMake([[[jsonJoint objectForKey:@"localAnchorB"] objectAtIndex:0] floatValue], -[[[jsonJoint objectForKey:@"localAnchorB"] objectAtIndex:1] floatValue]); 103 | anchorB.x += bodyB.node.position.x; 104 | anchorB.y += bodyB.node.position.y; 105 | float dampingRatio = [[jsonJoint objectForKey:@"dampingRatio"] floatValue]; 106 | float frequency = [[jsonJoint objectForKey:@"frequencyHZ"] floatValue]; 107 | 108 | SKPhysicsJointSpring *joint = [SKPhysicsJointSpring jointWithBodyA:bodyA bodyB:bodyB anchorA:anchorA anchorB:anchorB]; 109 | [joint setDamping:dampingRatio]; 110 | [joint setFrequency:frequency]; 111 | [scene.physicsWorld addJoint:joint]; 112 | 113 | [loadedJoints addObject:joint]; 114 | } 115 | else if (jointType == JOINT_WELD){ 116 | int indexA = [[jsonJoint objectForKey:@"bodyA"] integerValue]; 117 | int indexB = [[jsonJoint objectForKey:@"bodyB"] integerValue]; 118 | SKPhysicsBody *bodyA = [loadedBodies objectAtIndex:indexA]; 119 | SKPhysicsBody *bodyB = [loadedBodies objectAtIndex:indexB]; 120 | CGPoint anchorA = CGPointMake([[[jsonJoint objectForKey:@"localAnchorA"] objectAtIndex:0] floatValue], -[[[jsonJoint objectForKey:@"localAnchorA"] objectAtIndex:1] floatValue]); 121 | anchorA.x += bodyA.node.position.x; 122 | anchorA.y += bodyA.node.position.y; 123 | CGPoint anchorB = CGPointMake([[[jsonJoint objectForKey:@"localAnchorB"] objectAtIndex:0] floatValue], -[[[jsonJoint objectForKey:@"localAnchorB"] objectAtIndex:1] floatValue]); 124 | anchorB.x += bodyB.node.position.x; 125 | anchorB.y += bodyB.node.position.y; 126 | float referenceAngle = -[[jsonJoint objectForKey:@"referenceAngle"] floatValue] * M_PI / 180.0; 127 | 128 | SKPhysicsJointFixed *joint = [SKPhysicsJointFixed jointWithBodyA:bodyA bodyB:bodyB anchor:anchorB]; 129 | [scene.physicsWorld addJoint:joint]; 130 | 131 | [loadedJoints addObject:joint]; 132 | } 133 | else if (jointType == JOINT_REVOLUTE){ 134 | int indexA = [[jsonJoint objectForKey:@"bodyA"] integerValue]; 135 | int indexB = [[jsonJoint objectForKey:@"bodyB"] integerValue]; 136 | SKPhysicsBody *bodyA = [loadedBodies objectAtIndex:indexA]; 137 | SKPhysicsBody *bodyB = [loadedBodies objectAtIndex:indexB]; 138 | CGPoint anchorA = CGPointMake([[[jsonJoint objectForKey:@"localAnchorA"] objectAtIndex:0] floatValue], -[[[jsonJoint objectForKey:@"localAnchorA"] objectAtIndex:1] floatValue]); 139 | anchorA.x += bodyA.node.position.x; 140 | anchorA.y += bodyA.node.position.y; 141 | CGPoint anchorB = CGPointMake([[[jsonJoint objectForKey:@"localAnchorB"] objectAtIndex:0] floatValue], -[[[jsonJoint objectForKey:@"localAnchorB"] objectAtIndex:1] floatValue]); 142 | anchorB.x += bodyB.node.position.x; 143 | anchorB.y += bodyB.node.position.y; 144 | bool enableLimit = [[jsonJoint objectForKey:@"enableLimit"] boolValue]; 145 | float upperAngle = -[[jsonJoint objectForKey:@"lowerAngle"] floatValue] * M_PI / 180.0; 146 | float lowerAngle = -[[jsonJoint objectForKey:@"upperAngle"] floatValue] * M_PI / 180.0; 147 | float referenceAngle = -[[jsonJoint objectForKey:@"referenceAngle"] floatValue] * M_PI / 180.0; 148 | bool enableMotor = [[jsonJoint objectForKey:@"enableMotor"] boolValue]; 149 | float motorSpeed = -[[jsonJoint objectForKey:@"motorSpeed"] floatValue] * M_PI / 180; 150 | float maxMotorTorque = [[jsonJoint objectForKey:@"maxMotorTorque"] floatValue]; 151 | 152 | SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA:bodyA bodyB:bodyB anchor:anchorB]; 153 | [joint setShouldEnableLimits:enableLimit]; 154 | [joint setUpperAngleLimit:upperAngle]; 155 | [joint setLowerAngleLimit:lowerAngle]; 156 | [joint setRotationSpeed:motorSpeed]; 157 | [scene.physicsWorld addJoint:joint]; 158 | 159 | [loadedJoints addObject:joint]; 160 | } 161 | else if (jointType == JOINT_ROPE){ 162 | int indexA = [[jsonJoint objectForKey:@"bodyA"] integerValue]; 163 | int indexB = [[jsonJoint objectForKey:@"bodyB"] integerValue]; 164 | SKPhysicsBody *bodyA = [loadedBodies objectAtIndex:indexA]; 165 | SKPhysicsBody *bodyB = [loadedBodies objectAtIndex:indexB]; 166 | CGPoint anchorA = CGPointMake([[[jsonJoint objectForKey:@"localAnchorA"] objectAtIndex:0] floatValue], -[[[jsonJoint objectForKey:@"localAnchorA"] objectAtIndex:1] floatValue]); 167 | anchorA.x += bodyA.node.position.x; 168 | anchorA.y += bodyA.node.position.y; 169 | CGPoint anchorB = CGPointMake([[[jsonJoint objectForKey:@"localAnchorB"] objectAtIndex:0] floatValue], -[[[jsonJoint objectForKey:@"localAnchorB"] objectAtIndex:1] floatValue]); 170 | anchorB.x += bodyB.node.position.x; 171 | anchorB.y += bodyB.node.position.y; 172 | float maxLength = [[jsonJoint objectForKey:@"maxLength"] floatValue]; 173 | 174 | SKPhysicsJointLimit *joint = [SKPhysicsJointLimit jointWithBodyA:bodyA bodyB:bodyB anchorA:anchorA anchorB:anchorB]; 175 | [joint setMaxLength:maxLength]; 176 | [scene.physicsWorld addJoint:joint]; 177 | 178 | [loadedJoints addObject:joint]; 179 | } 180 | } 181 | } 182 | 183 | -(void) loadJsonFixtures : (NSArray*)jsonFixtures : (SKNode*) parent{ 184 | NSMutableArray *bodies = [[NSMutableArray alloc] init]; 185 | 186 | for (int i = 0, numberOfFixtures = [jsonFixtures count]; i < numberOfFixtures; i++){ 187 | 188 | // load fixture data 189 | NSDictionary* jsonFixture = [jsonFixtures objectAtIndex:i]; 190 | float density = [[jsonFixture objectForKey:@"density"] floatValue], 191 | friction = [[jsonFixture objectForKey:@"friction"] floatValue], 192 | restitution = [[jsonFixture objectForKey:@"restitution"] floatValue]; 193 | int maskBits = [[jsonFixture objectForKey:@"maskBits"] integerValue], 194 | categoryBits = [[jsonFixture objectForKey:@"categorykBits"] integerValue], 195 | groupIndex = [[jsonFixture objectForKey:@"groupIndex"] integerValue]; 196 | 197 | // load shapes and create physics body 198 | NSArray *jsonShapes = [jsonFixture objectForKey:@"shapes"]; 199 | for (int j = 0, numberOfShapes = [jsonShapes count]; j < numberOfShapes; j++){ 200 | NSDictionary *jsonShape = [jsonShapes objectAtIndex:j]; 201 | 202 | NSArray *position = [jsonShape objectForKey:@"position"]; 203 | CGPoint pos = CGPointMake([[position objectAtIndex:0] floatValue], -[[position objectAtIndex:1] floatValue]); 204 | 205 | SKPhysicsBody *body; 206 | 207 | int shapeType = [[jsonShape objectForKey:@"type"] integerValue]; 208 | if (shapeType == SHAPE_BOX){ 209 | float width = [[jsonShape objectForKey:@"width"] floatValue]; 210 | float height = [[jsonShape objectForKey:@"height"] floatValue]; 211 | body = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(width, height) center: pos]; 212 | } 213 | else if (shapeType == SHAPE_CIRCLE){ 214 | float radius = [[jsonShape objectForKey:@"radius"] floatValue]; 215 | body = [SKPhysicsBody bodyWithCircleOfRadius:radius * 2 center:pos]; 216 | } 217 | else if (shapeType == SHAPE_POLYGON){ 218 | NSArray *jsonVerts = [jsonShape objectForKey:@"vertices"]; 219 | 220 | CGMutablePathRef path = CGPathCreateMutable(); 221 | for (int k = 0, numberOfVertices = [jsonVerts count]; k < numberOfVertices; k++){ 222 | float x = pos.x + [[[jsonVerts objectAtIndex:k] objectAtIndex:0] floatValue]; 223 | float y = pos.y - [[[jsonVerts objectAtIndex:k] objectAtIndex:1] floatValue]; 224 | 225 | if (k == 0){ 226 | CGPathMoveToPoint(path, NULL, x, y); 227 | } 228 | else { 229 | CGPathAddLineToPoint(path, NULL, x, y); 230 | } 231 | 232 | } 233 | 234 | body = [SKPhysicsBody bodyWithPolygonFromPath:path]; 235 | } 236 | else if (shapeType == SHAPE_CHAIN){ 237 | NSArray *jsonVerts = [jsonShape objectForKey:@"vertices"]; 238 | 239 | CGMutablePathRef path = CGPathCreateMutable(); 240 | for (int k = 0, numberOfVertices = [jsonVerts count]; k < numberOfVertices; k++){ 241 | float x = pos.x + [[[jsonVerts objectAtIndex:k] objectAtIndex:0] floatValue]; 242 | float y = pos.y - [[[jsonVerts objectAtIndex:k] objectAtIndex:1] floatValue]; 243 | 244 | if (k == 0){ 245 | CGPathMoveToPoint(path, NULL, x, y); 246 | } 247 | else { 248 | CGPathAddLineToPoint(path, NULL, x, y); 249 | } 250 | 251 | } 252 | 253 | body = [SKPhysicsBody bodyWithEdgeChainFromPath:path]; 254 | } 255 | 256 | [body setDensity:density]; 257 | [body setFriction:friction]; 258 | [body setRestitution:restitution]; 259 | 260 | // [body setCategoryBitMask:categoryBits]; 261 | // [body setCollisionBitMask:maskBits]; 262 | // [body setContactTestBitMask:groupIndex]; 263 | 264 | [bodies addObject:body]; 265 | 266 | } 267 | 268 | } 269 | 270 | parent.physicsBody = [SKPhysicsBody bodyWithBodies:bodies]; 271 | } 272 | 273 | @end -------------------------------------------------------------------------------- /resources/loaders/Unity3D/DebugRenderer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class DebugRenderer : MonoBehaviour { 5 | 6 | void OnDrawGizmos(){ 7 | Collider2D[] colliders = GetComponents(); 8 | for (int i = 0, numberOfColliders = colliders.Length; i < numberOfColliders; i++){ 9 | if ((colliders[i] as BoxCollider2D) != null){ 10 | BoxCollider2D collider = (BoxCollider2D) colliders[i]; 11 | float width = collider.size.x; 12 | float height = collider.size.y; 13 | Vector3 position = transform.TransformPoint(new Vector3(collider.offset.x, collider.offset.y, 0)); 14 | UnityEditor.Handles.matrix = Matrix4x4.TRS(position, transform.rotation, transform.localScale); 15 | 16 | Vector3 from = new Vector3(-width / 2, height / 2, 0); 17 | Vector3 to = new Vector3(width / 2, height / 2); 18 | UnityEditor.Handles.DrawLine(from, to); 19 | 20 | from = new Vector3(width / 2, height / 2, 0); 21 | to = new Vector3(width / 2, -height / 2); 22 | UnityEditor.Handles.DrawLine(from, to); 23 | 24 | from = new Vector3(width / 2, -height / 2, 0); 25 | to = new Vector3(-width / 2, -height / 2); 26 | UnityEditor.Handles.DrawLine(from, to); 27 | 28 | from = new Vector3(-width / 2, -height / 2, 0); 29 | to = new Vector3(-width / 2, height / 2); 30 | UnityEditor.Handles.DrawLine(from, to); 31 | } 32 | else if ((colliders[i] as CircleCollider2D) != null){ 33 | CircleCollider2D collider = (CircleCollider2D) colliders[i]; 34 | float radius = collider.radius; 35 | Vector3 position = transform.TransformPoint(new Vector3(collider.offset.x, collider.offset.y, 0)); 36 | UnityEditor.Handles.matrix = Matrix4x4.TRS(position, transform.rotation, transform.localScale); 37 | UnityEditor.Handles.DrawWireDisc(new Vector3(), new Vector3(0, 0, 1), radius); 38 | } 39 | else if ((colliders[i] as PolygonCollider2D) != null){ 40 | PolygonCollider2D collider = (PolygonCollider2D) colliders[i]; 41 | Vector2[] vertices = collider.points; 42 | Vector3 position = transform.TransformPoint(new Vector3(collider.offset.x, collider.offset.y, 0)); 43 | UnityEditor.Handles.matrix = Matrix4x4.TRS(position, transform.rotation, transform.localScale); 44 | for (int j = 0; j < vertices.Length; j++){ 45 | if (j < vertices.Length - 1){ 46 | Vector3 from = new Vector3(vertices[j].x, vertices[j].y, 0); 47 | Vector3 to = new Vector3(vertices[j + 1].x, vertices[j + 1].y, 0); 48 | UnityEditor.Handles.DrawLine(from, to); 49 | } 50 | else if (j == vertices.Length - 1){ 51 | Vector3 from = new Vector3(vertices[j].x, vertices[j].y, 0); 52 | Vector3 to = new Vector3(vertices[0].x, vertices[0].y, 0); 53 | UnityEditor.Handles.DrawLine(from, to); 54 | } 55 | } 56 | } 57 | else if ((colliders[i] as EdgeCollider2D) != null){ 58 | EdgeCollider2D collider = (EdgeCollider2D) colliders[i]; 59 | Vector2[] vertices = collider.points; 60 | Vector3 position = transform.TransformPoint(new Vector3(collider.offset.x, collider.offset.y, 0)); 61 | UnityEditor.Handles.matrix = Matrix4x4.TRS(position, transform.rotation, transform.localScale); 62 | for (int j = 0; j < vertices.Length; j++){ 63 | if (j < vertices.Length - 1){ 64 | Vector3 from = new Vector3(vertices[j].x, vertices[j].y, 0); 65 | Vector3 to = new Vector3(vertices[j + 1].x, vertices[j + 1].y, 0); 66 | UnityEditor.Handles.DrawLine(from, to); 67 | } 68 | else if (j == vertices.Length - 1){ 69 | Vector3 from = new Vector3(vertices[j].x, vertices[j].y, 0); 70 | Vector3 to = new Vector3(vertices[0].x, vertices[0].y, 0); 71 | UnityEditor.Handles.DrawLine(from, to); 72 | } 73 | } 74 | } 75 | } 76 | // reset handles matrix 77 | UnityEditor.Handles.matrix = Matrix4x4.identity; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /resources/loaders/Unity3D/Fixture.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class Fixture { 5 | 6 | // physics material to be used by the collider 7 | public PhysicsMaterial2D physicsMaterial; 8 | // density of fixture 9 | public float density; 10 | // shape type (to be used when creating colliders) 11 | public int shapeType; 12 | // size of collier (Vetor2(width, height) for BoxCollider2D and Vector2(radius, radius) for CircleCollider2D) 13 | public Vector2 size; 14 | // offset position of collider from body 15 | public Vector2 offset; 16 | // vertices for creating PolygonCollider2D or Edgeollider2D 17 | public Vector2[] vertices; 18 | // is collider trigger 19 | public bool isTrigger; 20 | } 21 | -------------------------------------------------------------------------------- /resources/loaders/Unity3D/WorldLoader.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; // for List<> 4 | using SimpleJSON; // for parsing json file 5 | 6 | public class WorldLoader : MonoBehaviour{ 7 | 8 | /** meter to pixel ratio */ 9 | public float RATIO = 30; 10 | 11 | /** scene to load, file must be present in resources folder **/ 12 | public Object scene; 13 | 14 | private enum ShapeTypes { 15 | SHAPE_BOX, // BoxCollider2D 16 | SHAPE_CIRCLE, // CircleCollider2D 17 | SHAPE_POLYGON, // PolygonCollider2D 18 | SHAPE_CHAIN // EdgeCollider2D 19 | }; 20 | private enum JointTypes { 21 | JOINT_DISTANCE, // created as DistanceJoint2D 22 | JOINT_WELD, // not supported 23 | JOINT_REVOLUTE, // created as HingeJoint2D 24 | JOINT_WHEEL, // created as WheelJoint2D 25 | JOINT_PULLEY, // not supported 26 | JOINT_GEAR, // not supported 27 | JOINT_PRISMATIC, // created as SliderJoint2D 28 | JOINT_ROPE // created as DistanceJoint2D 29 | }; 30 | 31 | // list of loaded gameobjects 32 | [SerializeField] // so that entering play mode does not screw up "delete scene" feature 33 | [HideInInspector] // so that user does not screw with loaded objects 34 | private List loadedObjects; 35 | 36 | // list of loaded joints 37 | [SerializeField] 38 | [HideInInspector] 39 | private List loadedJoints; 40 | 41 | // resets loader to load new scene 42 | public void reset(){ 43 | if (loadedObjects != null){ 44 | loadedObjects.Clear(); 45 | loadedObjects = null; 46 | } 47 | loadedObjects = new List(); 48 | 49 | if (loadedJoints != null){ 50 | loadedJoints.Clear(); 51 | loadedJoints = null; 52 | } 53 | loadedJoints = new List(); 54 | } 55 | 56 | public List getLoadedObjects(){ 57 | return loadedObjects; 58 | } 59 | 60 | public List getLoadedJoints(){ 61 | return loadedJoints; 62 | } 63 | 64 | public void loadJsonScene(){ 65 | reset(); 66 | 67 | // load file 68 | TextAsset file = (TextAsset) Resources.Load(scene.name); 69 | // parse json file to scene 70 | JSONNode jsonScene = JSON.Parse(file.text); 71 | 72 | // load bodies 73 | JSONNode jsonBodies = jsonScene["bodies"]; 74 | loadJsonBodies(jsonBodies); 75 | 76 | // load joints 77 | JSONNode jsonJoints = jsonScene["joints"]; 78 | loadJsonJoints(jsonJoints); 79 | } 80 | 81 | void loadJsonJoints(JSONNode jsonJoints){ 82 | int JointCount = 0; 83 | 84 | for (int i = 0, numberOfJoints = jsonJoints.Count; i < numberOfJoints; i++){ 85 | JSONNode jsonJoint = jsonJoints[i]; 86 | int jointType = jsonJoint["jointType"].AsInt; 87 | 88 | GameObject bodyA = loadedObjects[jsonJoint["bodyA"].AsInt]; 89 | GameObject bodyB = loadedObjects[jsonJoint["bodyB"].AsInt]; 90 | 91 | JSONNode localAnchorA = jsonJoint["localAnchorA"]; 92 | Vector2 anchorA = new Vector2(localAnchorA[0].AsFloat / RATIO, -localAnchorA[1].AsFloat / RATIO); 93 | JSONNode localAnchorB = jsonJoint["localAnchorB"]; 94 | Vector2 anchorB = new Vector2(localAnchorB[0].AsFloat / RATIO, -localAnchorB[1].AsFloat / RATIO); 95 | bool collideConnected = jsonJoint["collideConnected"].AsBool; 96 | string userData = jsonJoint["userData"].Value; 97 | 98 | if (jointType == (int) JointTypes.JOINT_DISTANCE || jointType == (int) JointTypes.JOINT_ROPE){ 99 | DistanceJoint2D joint = bodyA.AddComponent(); 100 | joint.connectedBody = bodyB.GetComponent(); 101 | joint.anchor = anchorA; 102 | joint.connectedAnchor = anchorB; 103 | 104 | // distance joint 105 | if (jsonJoint["length"] != null){ 106 | joint.distance = jsonJoint["length"].AsFloat / RATIO; 107 | joint.maxDistanceOnly = true; 108 | } 109 | // rope joint 110 | else if (jsonJoint["maxLength"] != null){ 111 | joint.distance = jsonJoint["maxLength"].AsFloat / RATIO; 112 | } 113 | 114 | joint.enableCollision = collideConnected; 115 | joint.name += '_'; 116 | joint.name += userData.Length > 0 ? userData : "joint" + JointCount++; 117 | } 118 | else if (jointType == (int) JointTypes.JOINT_REVOLUTE){ 119 | HingeJoint2D joint = bodyA.AddComponent(); 120 | joint.connectedBody = bodyB.GetComponent(); 121 | joint.anchor = anchorA; 122 | joint.connectedAnchor = anchorB; 123 | joint.enableCollision = collideConnected; 124 | joint.name += '_'; 125 | joint.name += userData.Length > 0 ? userData : "joint" + JointCount++; 126 | 127 | // limits are not working properly 128 | bool enableLimits = jsonJoint["enableLimit"].AsBool; 129 | float referenceAngle = -jsonJoint["referenceAngle"].AsFloat; 130 | float angleBetweenBodies = Mathf.Atan2(bodyB.transform.position.y - bodyA.transform.position.y, 131 | bodyB.transform.position.x - bodyA.transform.position.x) * 180 / Mathf.PI; 132 | float upperAngle = -jsonJoint["lowerAngle"].AsFloat; 133 | float lowerAngle = -jsonJoint["upperAngle"].AsFloat; 134 | bool enableMotor = jsonJoint["enableMotor"].AsBool; 135 | float motorSpeed = -jsonJoint["motorSpeed"].AsFloat; 136 | float maxMotorTorque = jsonJoint["maxMotorTorque"].AsFloat; 137 | 138 | joint.useLimits = enableLimits; 139 | JointAngleLimits2D limits = new JointAngleLimits2D(); 140 | limits.max = angleBetweenBodies + upperAngle; 141 | limits.min = angleBetweenBodies + lowerAngle; 142 | joint.limits = limits; 143 | joint.useMotor = enableMotor; 144 | JointMotor2D motor = new JointMotor2D(); 145 | motor.maxMotorTorque = maxMotorTorque; 146 | motor.motorSpeed = motorSpeed; 147 | joint.motor = motor; 148 | } 149 | else if (jointType == (int) JointTypes.JOINT_WHEEL){ 150 | WheelJoint2D joint = bodyA.AddComponent(); 151 | joint.connectedBody = bodyB.GetComponent(); 152 | joint.anchor = anchorA; 153 | joint.connectedAnchor = anchorB; 154 | joint.enableCollision = collideConnected; 155 | joint.name += '_'; 156 | joint.name += userData.Length > 0 ? userData : "joint" + JointCount++; 157 | 158 | bool enableMotor = jsonJoint["enableMotor"].AsBool; 159 | float motorSpeed = -jsonJoint["motorSpeed"].AsFloat; 160 | float maxMotorTorque = jsonJoint["maxMotorTorque"].AsFloat; 161 | float dampingRatio = jsonJoint["dampingRatio"].AsFloat; 162 | float frequency = jsonJoint["frequencyHZ"].AsFloat; 163 | JSONNode localAxisA = jsonJoint["localAxisA"]; 164 | float angle = Mathf.Atan2(-localAxisA[1].AsFloat, localAxisA[0].AsFloat) * 180 / Mathf.PI; 165 | 166 | joint.useMotor = enableMotor; 167 | JointMotor2D motor = new JointMotor2D(); 168 | motor.maxMotorTorque = maxMotorTorque; 169 | motor.motorSpeed = motorSpeed; 170 | joint.motor = motor; 171 | 172 | JointSuspension2D suspension = new JointSuspension2D(); 173 | suspension.dampingRatio = dampingRatio; 174 | suspension.frequency = frequency; 175 | suspension.angle = angle; 176 | joint.suspension = suspension; 177 | } 178 | else if (jointType == (int) JointTypes.JOINT_PRISMATIC){ 179 | SliderJoint2D joint = bodyA.AddComponent(); 180 | joint.connectedBody = bodyB.GetComponent(); 181 | joint.anchor = anchorA; 182 | joint.connectedAnchor = anchorB; 183 | joint.enableCollision = collideConnected; 184 | joint.name += '_'; 185 | joint.name += userData.Length > 0 ? userData : "joint" + JointCount++; 186 | 187 | bool enableLimits = jsonJoint["enableLimit"].AsBool; 188 | float referenceAngle = -jsonJoint["referenceAngle"].AsFloat; 189 | float upperTranslation = jsonJoint["upperTranslation"].AsFloat / RATIO; 190 | float lowerTranslation = jsonJoint["lowerTranslation"].AsFloat / RATIO; 191 | bool enableMotor = jsonJoint["enableMotor"].AsBool; 192 | float motorSpeed = -jsonJoint["motorSpeed"].AsFloat; 193 | float maxMotorTorque = jsonJoint["maxMotorTorque"].AsFloat; 194 | JSONNode localAxisA = jsonJoint["localAxisA"]; 195 | float angle = Mathf.Atan2(-localAxisA[1].AsFloat, localAxisA[0].AsFloat) * 180 / Mathf.PI; 196 | 197 | joint.useLimits = enableLimits; 198 | JointTranslationLimits2D limits = new JointTranslationLimits2D(); 199 | limits.max = upperTranslation; 200 | limits.min = lowerTranslation; 201 | joint.limits = limits; 202 | joint.useMotor = enableMotor; 203 | JointMotor2D motor = new JointMotor2D(); 204 | motor.maxMotorTorque = maxMotorTorque; 205 | motor.motorSpeed = motorSpeed; 206 | joint.motor = motor; 207 | joint.angle = angle; 208 | } 209 | } 210 | } 211 | 212 | void loadJsonBodies(JSONNode jsonBodies){ 213 | int BodyCount = 0; 214 | 215 | for (int i = 0, numberOfBodies = jsonBodies.Count; i < numberOfBodies; i++){ 216 | JSONNode jsonBody = jsonBodies[i]; 217 | 218 | int bodyType = jsonBody["type"].AsInt; 219 | JSONNode pos = jsonBody["position"]; 220 | Vector3 position = new Vector3(pos[0].AsFloat / RATIO, -pos[1].AsFloat / RATIO, 0); 221 | float rotation = -jsonBody["rotation"].AsFloat; 222 | float linearDamping = jsonBody["linearDamping"].AsFloat; 223 | float angularDamping = jsonBody["angularDamping"].AsFloat; 224 | string userData = jsonBody["userData"].Value; 225 | bool isFixedRotation = jsonBody["isFixedRotation"].AsBool; 226 | bool isBullet = jsonBody["isBullet"].AsBool; 227 | 228 | 229 | GameObject body = new GameObject(userData.Length > 0 ? userData : "body" + BodyCount++); 230 | body.transform.position = position; 231 | body.transform.rotation = Quaternion.Euler(0, 0, rotation); 232 | body.AddComponent(); 233 | 234 | float density = 0; 235 | List fixtures = loadJsonFixtures(jsonBody["fixtures"]); 236 | for (int j = 0, numberOfFixtures = fixtures.Count; j < numberOfFixtures; j++){ 237 | Fixture fixture = fixtures[j]; 238 | density += fixture.density; 239 | if (fixture.shapeType == (int) ShapeTypes.SHAPE_BOX){ 240 | BoxCollider2D boxCollider = body.AddComponent(); 241 | boxCollider.isTrigger = fixture.isTrigger; 242 | boxCollider.offset = fixture.offset; 243 | boxCollider.size = fixture.size; 244 | boxCollider.sharedMaterial = fixture.physicsMaterial; 245 | } 246 | else if (fixture.shapeType == (int) ShapeTypes.SHAPE_CIRCLE){ 247 | CircleCollider2D circleCollider = body.AddComponent(); 248 | circleCollider.isTrigger = fixture.isTrigger; 249 | circleCollider.offset = fixture.offset; 250 | circleCollider.radius = fixture.size.x; 251 | circleCollider.sharedMaterial = fixture.physicsMaterial; 252 | } 253 | else if (fixture.shapeType == (int) ShapeTypes.SHAPE_POLYGON){ 254 | PolygonCollider2D polyCollider = body.AddComponent(); 255 | polyCollider.isTrigger = fixture.isTrigger; 256 | polyCollider.offset = fixture.offset; 257 | polyCollider.SetPath(0, fixture.vertices); 258 | polyCollider.sharedMaterial = fixture.physicsMaterial; 259 | } 260 | else if (fixture.shapeType == (int) ShapeTypes.SHAPE_CHAIN){ 261 | EdgeCollider2D edgeCollider = body.AddComponent(); 262 | edgeCollider.isTrigger = fixture.isTrigger; 263 | edgeCollider.offset = fixture.offset; 264 | edgeCollider.points = fixture.vertices; 265 | edgeCollider.sharedMaterial = fixture.physicsMaterial; 266 | } 267 | } 268 | 269 | body.AddComponent(); 270 | Rigidbody2D rigidBody2D = body.GetComponent(); 271 | rigidBody2D.isKinematic = bodyType == 1 || bodyType == 0; 272 | rigidBody2D.fixedAngle = isFixedRotation; 273 | rigidBody2D.mass = density; 274 | rigidBody2D.angularDrag = angularDamping; 275 | rigidBody2D.drag = linearDamping; 276 | 277 | if (isBullet){ 278 | rigidBody2D.collisionDetectionMode = CollisionDetectionMode2D.Continuous; 279 | } 280 | else { 281 | rigidBody2D.collisionDetectionMode = CollisionDetectionMode2D.None; 282 | } 283 | 284 | loadedObjects.Add(body); 285 | } 286 | } 287 | 288 | List loadJsonFixtures(JSONNode jsonFixtures){ 289 | List fixtures = new List(); 290 | 291 | for (int i = 0, numberOfFixtures = jsonFixtures.Count; i < numberOfFixtures; i++){ 292 | JSONNode jsonFixture = jsonFixtures[i]; 293 | 294 | float density = jsonFixture["density"].AsFloat; 295 | float friction = jsonFixture["friction"].AsFloat; 296 | float restitution = jsonFixture["restitution"].AsFloat; 297 | bool isSensor = jsonFixture["isSensor"].AsBool; 298 | 299 | JSONNode jsonShapes = jsonFixture["shapes"]; 300 | for (int j = 0, numberOfShapes = jsonShapes.Count; j < numberOfShapes; j++){ 301 | JSONNode jsonShape = jsonShapes[j]; 302 | 303 | JSONNode pos = jsonShape["position"]; 304 | Vector2 position = new Vector2(pos[0].AsFloat / RATIO, -pos[1].AsFloat / RATIO); 305 | int shapeType = jsonShape["type"].AsInt; 306 | 307 | Fixture fixture = new Fixture(); 308 | fixture.physicsMaterial = new PhysicsMaterial2D(); 309 | fixture.physicsMaterial.friction = friction; 310 | fixture.physicsMaterial.bounciness = restitution; 311 | fixture.density = density; 312 | 313 | if (shapeType == (int) ShapeTypes.SHAPE_BOX){ 314 | float width = jsonShape["width"].AsFloat / RATIO; 315 | float height = jsonShape["height"].AsFloat / RATIO; 316 | fixture.shapeType = (int) ShapeTypes.SHAPE_BOX; 317 | fixture.size = new Vector2(width, height); 318 | fixture.offset = position; 319 | } 320 | else if (shapeType == (int) ShapeTypes.SHAPE_CIRCLE){ 321 | float radius = 2 * jsonShape["radius"].AsFloat / RATIO; 322 | fixture.shapeType = (int) ShapeTypes.SHAPE_CIRCLE; 323 | fixture.size = new Vector2(radius, radius); 324 | fixture.offset = position; 325 | } 326 | else if (shapeType == (int) ShapeTypes.SHAPE_POLYGON){ 327 | JSONNode jsonVertices = jsonShape["vertices"]; 328 | fixture.vertices = new Vector2[jsonVertices.Count]; 329 | for (int k = 0, numberOfVertices = jsonVertices.Count; k < numberOfVertices; k++){ 330 | fixture.vertices[k] = new Vector2(jsonVertices[k][0].AsFloat / RATIO, 331 | -jsonVertices[k][1].AsFloat / RATIO); 332 | } 333 | fixture.shapeType = (int) ShapeTypes.SHAPE_POLYGON; 334 | fixture.offset = position; 335 | } 336 | else if (shapeType == (int) ShapeTypes.SHAPE_CHAIN){ 337 | JSONNode jsonVertices = jsonShape["vertices"]; 338 | fixture.vertices = new Vector2[jsonVertices.Count]; 339 | for (int k = 0, numberOfVertices = jsonVertices.Count; k < numberOfVertices; k++){ 340 | fixture.vertices[k] = new Vector2(jsonVertices[k][0].AsFloat / RATIO, 341 | -jsonVertices[k][1].AsFloat / RATIO); 342 | } 343 | fixture.shapeType = (int) ShapeTypes.SHAPE_CHAIN; 344 | fixture.offset = position; 345 | } 346 | fixture.isTrigger = isSensor; 347 | fixtures.Add(fixture); 348 | } 349 | 350 | } 351 | 352 | return fixtures; 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /resources/loaders/file_structure.json: -------------------------------------------------------------------------------- 1 | /* 2 | // important // 3 | all units are in pixels, so make sure to divide them by meter_to_pixel ratio (generally 30) 4 | the scene was made in web canvas, and therefore the y-axis is flipped, so y-axis coordinates must be multiplied by -1 5 | for loading scenes refer to sample loaders available in /Loaders directory 6 | */ 7 | 8 | /* 9 | Structure 10 | 11 | bodies : array of body 12 | type: STATIC(0), KINEMATIC(1), DYNAMIC(2) 13 | userData: userdata_provided_in_editor, used in collision detection 14 | position: [position_on_x_axis, position_on_y_axis] 15 | rotation: body's rotation in degrees 16 | isBullet: whether body is bullet or not (bullet bodies are expensive to simulate but there collision detection is better than normal bodies), avoid wherever psossible 17 | isFixedRotation: is rotation of body fixed, if true => body won't rotate 18 | fixtures: array of fixtures 19 | 20 | fixture : contains shape information and physical properties like density, friction.. 21 | isSensor: if true => fixture would be used only for collision detection but not collision resolution 22 | restitution: elasticity of coillison (if more, body would bounce more) 23 | friction 24 | density 25 | maskBits 26 | categoryBits 27 | groupIndex 28 | shapes: array of shapes sharing same fixture (concave shape is exported as seprate convex shapes having same physical properties) 29 | 30 | shape : the actual geometry 31 | type: BOX(0), CIRCLE(1), POLYGON(2), CHAIN(3) 32 | position: position in body's local space (with respect to bodies position) 33 | vertices: array of vertices 34 | 35 | vertices : [position_x, position_y] in shape's local space (position relative to shapes position) 36 | 37 | joints : array of joints 38 | jointType: DISTANCE(0), WELD(1), REVOLUTE(2), WHEEL(3), PULLEY(4), GEAR(5) 39 | bodyA: index of bodyA (primary anchor) 40 | bodyB: index of bodyB (secondary anchor) 41 | localAnchorA: [position_x, position_y], offset from bodyA's position (in bodyA's local space) 42 | localAnchorB: [position_x, position_y], offset from bodyB's position (in bodyB's local space) 43 | userData: userdata_provided_in_editor 44 | collideConnected: can bodyA and bodyB collide 45 | 46 | // distance and wheel joint parameters 47 | length: length of joint 48 | dampingRatio 49 | frequencyHZ 50 | 51 | // weld, revolute and prismatic joint paramters 52 | referenceAngle: angle of bodyB 53 | 54 | // revolute and wheel joint parameters 55 | enableMotor: can bodyB rotate on its own like a motor 56 | maxMotorTorque: maximum torque that can be applied on bodyB 57 | motorSpeed: speed of rotation (multiply by -1 because of y-axis flipping) 58 | 59 | // revolute joint parameters 60 | enableLimit: is rotation constrained 61 | lowerAngle: lower angle limit in degrees (might be -ve of upper angle limit for some engines because of y-axis flipping) 62 | upperAngle: upper angle limit in degrees (might be -ve of lower angle limit for some engines because of y-axis flipping) 63 | 64 | // wheel and prismatic joint parameters 65 | localAxisA: axis of joint (along which bodyB can translate) 66 | 67 | // pulley joint parameters 68 | groundAnchorA: anchor for bodyA (this anchor doesnot moves), in world space coordinates 69 | groundAnchorB: anchor for bodyB (this anchor doesnot moves), in world space coordinates 70 | lengthA = maxLenghtA 71 | lengthB = maxLenghtB 72 | ratio 73 | 74 | // gear joint parameters 75 | joint1: must be either revolute of prismatic joint 76 | joint2: must be either revolute of prismatic joint 77 | ratio: influence of driving joint 78 | 79 | // prismatic joint parameters 80 | enableLimit: is rotation constrained 81 | lowerTranslation: lower limit in pixels 82 | upperTranslation: upper limit in pixels 83 | enableMotor 84 | maxMotorForce: maximum force that can be applied on bodyB 85 | motorSpeed: speed of rotation (multiply by -1 because of y-axis flipping) 86 | 87 | */ 88 | 89 | { 90 | // array of bodies 91 | "bodies": [ 92 | { 93 | "type": integer, 94 | "userData": string, 95 | // array of fixtures 96 | "fixtures": [ 97 | { 98 | "isSensor": boolean, 99 | "restitution": float, 100 | "friction": float, 101 | "density": float, 102 | "maskBits": short, 103 | "categoryBits" short:, 104 | "groupIndex": short, 105 | // array of shapes 106 | "shapes": [ 107 | { 108 | "type": integer, 109 | "position": [ 110 | float_pos-x, 111 | float_pos-y 112 | ], 113 | // array of vertices 114 | "vertices": [ 115 | [ 116 | float_x, 117 | float_y 118 | ], 119 | [ 120 | float_x, 121 | float_y 122 | ].... 123 | ] 124 | } 125 | ] 126 | } 127 | ] 128 | "position": [ 129 | float_pos-x, 130 | float_pos-y 131 | ], 132 | "rotation": float_rotation-in-deg, 133 | "isBullet": boolean, 134 | "isFixedRotation": boolean 135 | } 136 | ], 137 | "joints": [ 138 | { 139 | "localAnchorA": [ 140 | 0, 141 | 0 142 | ], 143 | "localAnchorB": [ 144 | 0, 145 | 0 146 | ], 147 | "userData": "", 148 | "collideConnected": false, 149 | "jointType": 0, 150 | "bodyA": 0, 151 | "bodyB": 1, 152 | // joint specific parameters 153 | } 154 | ] 155 | } -------------------------------------------------------------------------------- /resources/monkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/monkey.jpg -------------------------------------------------------------------------------- /resources/pika.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/pika.bmp -------------------------------------------------------------------------------- /resources/pika.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/pika.png -------------------------------------------------------------------------------- /resources/title_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/title_icon.png -------------------------------------------------------------------------------- /resources/ui/crossair_blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/ui/crossair_blue.png -------------------------------------------------------------------------------- /resources/ui/crossair_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/ui/crossair_red.png -------------------------------------------------------------------------------- /resources/ui/crossair_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/ui/crossair_white.png -------------------------------------------------------------------------------- /resources/ui/pivot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amuTBKT/Physics-Editor/4ed6638855105680126d5ba252aaab395e667543/resources/ui/pivot.png --------------------------------------------------------------------------------