├── .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 | 
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 | 
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 |
112 |
113 |
114 |
116 |
118 |
120 |
121 |
122 |
132 |
133 |
134 |
212 |
213 |
359 |
360 |
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
--------------------------------------------------------------------------------