Skip to content

Commit 3fc588b

Browse files
Lizard-134ian
authored andcommitted
Add a new editor (with Polygon support) for Physics2 engine (#825)
* Physics editor and custom polygon support * Add shape preview * Dynamic dimension labels in function of the shape * Add polygon editor * Preview box, circle and edge * Prevent invalid properties values * Add BehaviorsEditorService
1 parent 50ebf6e commit 3fc588b

File tree

10 files changed

+1112
-96
lines changed

10 files changed

+1112
-96
lines changed

Extensions/Physics2Behavior/JsExtension.js

Lines changed: 66 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -49,86 +49,78 @@ module.exports = {
4949
}
5050
if (propertyName === 'shapeDimensionA') {
5151
newValue = parseFloat(newValue);
52-
if (newValue < 0) newValue = 0;
52+
if (newValue !== newValue) return false;
5353
behaviorContent.shapeDimensionA = newValue;
5454
return true;
5555
}
5656
if (propertyName === 'shapeDimensionB') {
5757
newValue = parseFloat(newValue);
58-
if (newValue < 0) newValue = 0;
58+
if (newValue !== newValue) return false;
5959
behaviorContent.shapeDimensionB = newValue;
6060
return true;
6161
}
6262
if (propertyName === 'shapeOffsetX') {
63-
behaviorContent.shapeOffsetX = parseFloat(newValue);
63+
newValue = parseFloat(newValue);
64+
if (newValue !== newValue) return false;
65+
behaviorContent.shapeOffsetX = newValue;
6466
return true;
6567
}
6668
if (propertyName === 'shapeOffsetY') {
67-
behaviorContent.shapeOffsetY = parseFloat(newValue);
69+
newValue = parseFloat(newValue);
70+
if (newValue !== newValue) return false;
71+
behaviorContent.shapeOffsetY = newValue;
72+
return true;
73+
}
74+
if (propertyName === 'polygonOrigin') {
75+
behaviorContent.polygonOrigin = newValue;
76+
return true;
77+
}
78+
if (propertyName === 'vertices') {
79+
behaviorContent.vertices = JSON.parse(newValue);
6880
return true;
6981
}
7082
if (propertyName === 'density') {
71-
newValue = parseFloat(newValue);
72-
if (newValue < 0) newValue = 0;
73-
behaviorContent.density = newValue;
83+
behaviorContent.density = parseFloat(newValue);
7484
return true;
7585
}
7686
if (propertyName === 'friction') {
7787
newValue = parseFloat(newValue);
78-
if (newValue < 0) newValue = 0;
88+
if (newValue !== newValue) return false;
7989
behaviorContent.friction = newValue;
8090
return true;
8191
}
8292
if (propertyName === 'restitution') {
8393
newValue = parseFloat(newValue);
84-
if (newValue < 0) newValue = 0;
94+
if (newValue !== newValue) return false;
8595
behaviorContent.restitution = newValue;
8696
return true;
8797
}
8898
if (propertyName === 'linearDamping') {
89-
behaviorContent.linearDamping = parseFloat(newValue);
99+
newValue = parseFloat(newValue);
100+
if (newValue !== newValue) return false;
101+
behaviorContent.linearDamping = newValue;
90102
return true;
91103
}
92104
if (propertyName === 'angularDamping') {
93-
behaviorContent.angularDamping = parseFloat(newValue);
105+
newValue = parseFloat(newValue);
106+
if (newValue !== newValue) return false;
107+
behaviorContent.angularDamping = newValue;
94108
return true;
95109
}
96110
if (propertyName === 'gravityScale') {
97-
behaviorContent.gravityScale = parseFloat(newValue);
111+
newValue = parseFloat(newValue);
112+
if (newValue !== newValue) return false;
113+
behaviorContent.gravityScale = newValue;
98114
return true;
99115
}
100116
if (propertyName === 'layers') {
101-
// The given binary string is reverse, fix it
102-
newValue = newValue
103-
.split('')
104-
.reverse()
105-
.join('');
106-
// Convert it into a decimal
107-
newValue = parseInt(newValue, 2);
108-
// If it can't be converted, cancel the edit
109-
if (isNaN(newValue)) return false;
110-
// Layers minimum and maximum values
111-
if (newValue < 0) newValue = 0;
112-
if (newValue > 65535) newValue = 65535; // 65535 is the decimal form of 1111111111111111 (16 layer bits flagged)
113-
// Save the valid decimal
114-
behaviorContent.layers = newValue;
117+
behaviorContent.layers = parseInt(newValue);
115118
return true;
116119
}
117120
if (propertyName === 'masks') {
118-
// Same than layers
119-
newValue = newValue
120-
.split('')
121-
.reverse()
122-
.join('');
123-
newValue = parseInt(newValue, 2);
124-
if (isNaN(newValue)) return false;
125-
if (newValue < 0) newValue = 0;
126-
if (newValue > 65535) newValue = 65535;
127-
behaviorContent.masks = newValue;
121+
behaviorContent.masks = parseInt(newValue);
128122
return true;
129123
}
130-
131-
return false;
132124
};
133125
physics2Behavior.getProperties = function(behaviorContent) {
134126
var behaviorProperties = new gd.MapStringPropertyDescriptor();
@@ -169,8 +161,8 @@ module.exports = {
169161
.setLabel('Shape')
170162
.addExtraInfo('Box')
171163
.addExtraInfo('Circle')
172-
// .addExtraInfo("Polygon") Needs an editor to be useful
173164
.addExtraInfo('Edge')
165+
.addExtraInfo('Polygon')
174166
);
175167
behaviorProperties.set(
176168
'shapeDimensionA',
@@ -196,6 +188,21 @@ module.exports = {
196188
.setType('Number')
197189
.setLabel('Shape Offset Y')
198190
);
191+
behaviorProperties.set(
192+
'polygonOrigin',
193+
new gd.PropertyDescriptor(behaviorContent.polygonOrigin || 'Center')
194+
.setType('Choice')
195+
.setLabel('Polygon Origin')
196+
.addExtraInfo('Center')
197+
.addExtraInfo('Origin')
198+
.addExtraInfo('TopLeft')
199+
);
200+
behaviorProperties.set(
201+
'vertices',
202+
new gd.PropertyDescriptor(
203+
JSON.stringify(behaviorContent.vertices || [])
204+
).setLabel('Vertices')
205+
);
199206
behaviorProperties.set(
200207
'density',
201208
new gd.PropertyDescriptor(behaviorContent.density.toString(10))
@@ -232,38 +239,22 @@ module.exports = {
232239
.setType('Number')
233240
.setLabel('Gravity Scale')
234241
);
235-
236-
// Waiting for a layers/masks editor
237-
/*
238-
// Transform the layers number into a binary string
239-
var layers = behaviorContent.layers.toString(2);
240-
// Reverse the string (so the first layer bit is shown at the left)
241-
layers = layers
242-
.split('')
243-
.reverse()
244-
.join('');
245-
// Add zeros until the total size is 16
246-
if (layers.length < 16) layers = layers + '0'.repeat(16 - layers.length);
247-
// Expose the converted string
248242
behaviorProperties.set(
249243
'layers',
250-
new gd.PropertyDescriptor(layers).setLabel('Layers')
244+
new gd.PropertyDescriptor(behaviorContent.layers.toString(10))
245+
.setType('Number')
246+
.setLabel('Layers')
251247
);
252-
// Same than layers
253-
var masks = behaviorContent.masks.toString(2);
254-
masks = masks
255-
.split('')
256-
.reverse()
257-
.join('');
258-
if (masks.length < 16) masks = masks + '0'.repeat(16 - masks.length);
259248
behaviorProperties.set(
260249
'masks',
261-
new gd.PropertyDescriptor(masks).setLabel('Masks')
250+
new gd.PropertyDescriptor(behaviorContent.masks.toString(10))
251+
.setType('Number')
252+
.setLabel('Masks')
262253
);
263-
*/
264254

265255
return behaviorProperties;
266256
};
257+
267258
physics2Behavior.setRawJSONContent(
268259
JSON.stringify({
269260
type: 'Dynamic',
@@ -275,6 +266,8 @@ module.exports = {
275266
shapeDimensionB: 0,
276267
shapeOffsetX: 0,
277268
shapeOffsetY: 0,
269+
polygonOrigin: 'Center',
270+
vertices: [],
278271
density: 1.0,
279272
friction: 0.3,
280273
restitution: 0.1,
@@ -293,23 +286,27 @@ module.exports = {
293286
newValue
294287
) {
295288
if (propertyName === 'gravityX') {
296-
sharedContent.gravityX = parseInt(newValue, 10);
289+
newValue = parseFloat(newValue);
290+
if (newValue !== newValue) return false;
291+
behaviorContent.gravityX = newValue;
297292
return true;
298293
}
299294
if (propertyName === 'gravityY') {
300-
sharedContent.gravityY = parseInt(newValue, 10);
295+
newValue = parseFloat(newValue);
296+
if (newValue !== newValue) return false;
297+
behaviorContent.gravityY = newValue;
301298
return true;
302299
}
303300
if (propertyName === 'scaleX') {
304301
newValue = parseInt(newValue, 10);
305-
if (newValue <= 0) newValue = 1;
306-
sharedContent.scaleX = newValue;
302+
if (newValue !== newValue) return false;
303+
behaviorContent.scaleX = newValue;
307304
return true;
308305
}
309306
if (propertyName === 'scaleY') {
310307
newValue = parseInt(newValue, 10);
311-
if (newValue <= 0) newValue = 1;
312-
sharedContent.scaleY = newValue;
308+
if (newValue !== newValue) return false;
309+
behaviorContent.scaleY = newValue;
313310
return true;
314311
}
315312

@@ -721,7 +718,7 @@ module.exports = {
721718
t(
722719
'Modify an object shape scale. It affects custom shape dimensions and shape offset, if custom dimensions are not set the body will be scaled automatically to the object size.'
723720
),
724-
t('Do to _PARAM2__PARAM3_ to the shape scale of _PARAM0_'),
721+
t('Do _PARAM2__PARAM3_ to the shape scale of _PARAM0_'),
725722
t('Body settings'),
726723
'res/physics24.png',
727724
'res/physics16.png'

Extensions/Physics2Behavior/physics2runtimebehavior.js

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ gdjs.Physics2RuntimeBehavior = function(runtimeScene, behaviorData, owner) {
203203
this.shapeDimensionB = behaviorData.content.shapeDimensionB;
204204
this.shapeOffsetX = behaviorData.content.shapeOffsetX;
205205
this.shapeOffsetY = behaviorData.content.shapeOffsetY;
206+
this.polygonOrigin = behaviorData.content.polygonOrigin;
207+
this.polygon = this.getPolygon(behaviorData.content.vertices);
206208
this.density = behaviorData.content.density;
207209
this.friction = behaviorData.content.friction;
208210
this.restitution = behaviorData.content.restitution;
@@ -227,6 +229,7 @@ gdjs.Physics2RuntimeBehavior = function(runtimeScene, behaviorData, owner) {
227229
this._objectOldAngle = 0;
228230
this._objectOldWidth = 0;
229231
this._objectOldHeight = 0;
232+
this._verticesBuffer = 0; // Stores a Box2D pointer of created vertices
230233
};
231234

232235
gdjs.Physics2RuntimeBehavior.prototype = Object.create(
@@ -251,6 +254,11 @@ gdjs.Physics2RuntimeBehavior.prototype.onDeActivate = function() {
251254
if (this._body !== null) {
252255
// When a body is deleted, Box2D removes automatically its joints, leaving an invalid pointer in our joints list
253256
this._sharedData.clearBodyJoints(this._body);
257+
// Delete the vertices
258+
if (this._verticesBuffer) {
259+
Box2D._free(this._verticesBuffer);
260+
this._verticesBuffer = 0;
261+
}
254262
// Delete the body
255263
this._sharedData.world.DestroyBody(this._body);
256264
this._body = null;
@@ -261,6 +269,15 @@ gdjs.Physics2RuntimeBehavior.prototype.ownerRemovedFromScene = function() {
261269
this.onDeActivate();
262270
};
263271

272+
gdjs.Physics2RuntimeBehavior.prototype.getPolygon = function(verticesData) {
273+
var polygon = new gdjs.Polygon();
274+
var maxVertices = 8;
275+
for (var i = 0, len = verticesData.length; i < Math.min(len, maxVertices); i++) {
276+
polygon.vertices.push([verticesData[i].x, verticesData[i].y]);
277+
}
278+
return polygon;
279+
};
280+
264281
gdjs.Physics2RuntimeBehavior.prototype.createShape = function() {
265282
// Get the scaled offset
266283
var offsetX = this.shapeOffsetX
@@ -290,7 +307,55 @@ gdjs.Physics2RuntimeBehavior.prototype.createShape = function() {
290307
// Set the offset
291308
shape.set_m_p(this.b2Vec2(offsetX, offsetY));
292309
} else if (this.shape === 'Polygon') {
293-
// Needs a vertices editor
310+
shape = new Box2D.b2PolygonShape();
311+
// Not convex, fall back to a box
312+
if (!this.polygon.isConvex()) {
313+
var width =
314+
(this.owner.getWidth() > 0 ? this.owner.getWidth() : 1) *
315+
this._sharedData.invScaleX;
316+
var height =
317+
(this.owner.getHeight() > 0 ? this.owner.getHeight() : 1) *
318+
this._sharedData.invScaleY;
319+
// Set the shape box
320+
shape.SetAsBox(width / 2, height / 2, this.b2Vec2(offsetX, offsetY), 0);
321+
} else {
322+
var originOffsetX = 0;
323+
var originOffsetY = 0;
324+
if(this.polygonOrigin === "Origin"){
325+
originOffsetX = (this.owner.getWidth() > 0 ? -this.owner.getWidth()/2 : 0)
326+
+ (this.owner.getX() - this.owner.getDrawableX());
327+
originOffsetY = (this.owner.getHeight() > 0 ? -this.owner.getHeight()/2 : 0)
328+
+ (this.owner.getY() - this.owner.getDrawableY());
329+
}
330+
else if(this.polygonOrigin === "TopLeft"){
331+
originOffsetX = this.owner.getWidth() > 0 ? -this.owner.getWidth()/2 : 0;
332+
originOffsetY = this.owner.getHeight() > 0 ? -this.owner.getHeight()/2 : 0;
333+
}
334+
// Generate vertices if not done already
335+
if (!this._verticesBuffer) {
336+
// Store the vertices using a memory allocation function
337+
var buffer = Box2D._malloc(
338+
this.polygon.vertices.length * 8,
339+
'float',
340+
Box2D.ALLOC_STACK
341+
);
342+
this._verticesBuffer = buffer;
343+
}
344+
// Overwrite the vertices stored in the buffer
345+
var offset = 0;
346+
for (var i = 0, len = this.polygon.vertices.length; i < len; i++) {
347+
Box2D.HEAPF32[(this._verticesBuffer + offset) >> 2] =
348+
(this.polygon.vertices[i][0] * this.shapeScale + originOffsetX) * this._sharedData.invScaleX +
349+
offsetX;
350+
Box2D.HEAPF32[(this._verticesBuffer + (offset + 4)) >> 2] =
351+
(this.polygon.vertices[i][1] * this.shapeScale + originOffsetY) * this._sharedData.invScaleY +
352+
offsetY;
353+
offset += 8;
354+
}
355+
// Set the shape vertices
356+
var b2Vertices = Box2D.wrapPointer(this._verticesBuffer, Box2D.b2Vec2);
357+
shape.Set(b2Vertices, this.polygon.vertices.length);
358+
}
294359
} else if (this.shape === 'Edge') {
295360
shape = new Box2D.b2EdgeShape();
296361
// Length from the custom dimension or from the object width
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// @flow
2+
import BehaviorPropertiesEditor from './Editors/BehaviorPropertiesEditor';
3+
import Physics2Editor from './Editors/Physics2Editor';
4+
5+
/**
6+
* A service returning editor components for each behavior type.
7+
*/
8+
export default {
9+
getEditor(behaviorType: string) {
10+
if (!this.components[behaviorType]) {
11+
return BehaviorPropertiesEditor; // Default properties editor
12+
}
13+
return this.components[behaviorType].component; // Custom behavior editor
14+
},
15+
components: {
16+
'Physics2::Physics2Behavior': {
17+
component: Physics2Editor,
18+
},
19+
},
20+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {
2+
type ResourceSource,
3+
type ChooseResourceFunction,
4+
} from '../../ResourcesList/ResourceSource.flow';
5+
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';
6+
7+
/**
8+
* The props given to any behavior editor
9+
*/
10+
export type BehaviorEditorProps = {|
11+
behavior: Object,
12+
project: gdProject,
13+
resourceSources: Array<ResourceSource>,
14+
onChooseResource: ChooseResourceFunction,
15+
resourceExternalEditors: Array<ResourceExternalEditor>,
16+
|};

0 commit comments

Comments
 (0)