Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Physics behavior editor #825

Merged
merged 11 commits into from
Jan 5, 2019
133 changes: 65 additions & 68 deletions Extensions/Physics2Behavior/JsExtension.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,86 +49,78 @@ module.exports = {
}
if (propertyName === 'shapeDimensionA') {
newValue = parseFloat(newValue);
if (newValue < 0) newValue = 0;
if (newValue !== newValue) return false;
behaviorContent.shapeDimensionA = newValue;
return true;
}
if (propertyName === 'shapeDimensionB') {
newValue = parseFloat(newValue);
if (newValue < 0) newValue = 0;
if (newValue !== newValue) return false;
behaviorContent.shapeDimensionB = newValue;
return true;
}
if (propertyName === 'shapeOffsetX') {
behaviorContent.shapeOffsetX = parseFloat(newValue);
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.shapeOffsetX = newValue;
return true;
}
if (propertyName === 'shapeOffsetY') {
behaviorContent.shapeOffsetY = parseFloat(newValue);
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.shapeOffsetY = newValue;
return true;
}
if (propertyName === 'polygonOrigin') {
behaviorContent.polygonOrigin = newValue;
return true;
}
if (propertyName === 'vertices') {
behaviorContent.vertices = JSON.parse(newValue);
return true;
}
if (propertyName === 'density') {
newValue = parseFloat(newValue);
if (newValue < 0) newValue = 0;
behaviorContent.density = newValue;
behaviorContent.density = parseFloat(newValue);
return true;
}
if (propertyName === 'friction') {
newValue = parseFloat(newValue);
if (newValue < 0) newValue = 0;
if (newValue !== newValue) return false;
behaviorContent.friction = newValue;
return true;
}
if (propertyName === 'restitution') {
newValue = parseFloat(newValue);
if (newValue < 0) newValue = 0;
if (newValue !== newValue) return false;
behaviorContent.restitution = newValue;
return true;
}
if (propertyName === 'linearDamping') {
behaviorContent.linearDamping = parseFloat(newValue);
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.linearDamping = newValue;
return true;
}
if (propertyName === 'angularDamping') {
behaviorContent.angularDamping = parseFloat(newValue);
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.angularDamping = newValue;
return true;
}
if (propertyName === 'gravityScale') {
behaviorContent.gravityScale = parseFloat(newValue);
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.gravityScale = newValue;
return true;
}
if (propertyName === 'layers') {
// The given binary string is reverse, fix it
newValue = newValue
.split('')
.reverse()
.join('');
// Convert it into a decimal
newValue = parseInt(newValue, 2);
// If it can't be converted, cancel the edit
if (isNaN(newValue)) return false;
// Layers minimum and maximum values
if (newValue < 0) newValue = 0;
if (newValue > 65535) newValue = 65535; // 65535 is the decimal form of 1111111111111111 (16 layer bits flagged)
// Save the valid decimal
behaviorContent.layers = newValue;
behaviorContent.layers = parseInt(newValue);
return true;
}
if (propertyName === 'masks') {
// Same than layers
newValue = newValue
.split('')
.reverse()
.join('');
newValue = parseInt(newValue, 2);
if (isNaN(newValue)) return false;
if (newValue < 0) newValue = 0;
if (newValue > 65535) newValue = 65535;
behaviorContent.masks = newValue;
behaviorContent.masks = parseInt(newValue);
return true;
}

return false;
};
physics2Behavior.getProperties = function(behaviorContent) {
var behaviorProperties = new gd.MapStringPropertyDescriptor();
Expand Down Expand Up @@ -169,8 +161,8 @@ module.exports = {
.setLabel('Shape')
.addExtraInfo('Box')
.addExtraInfo('Circle')
// .addExtraInfo("Polygon") Needs an editor to be useful
.addExtraInfo('Edge')
.addExtraInfo('Polygon')
);
behaviorProperties.set(
'shapeDimensionA',
Expand All @@ -196,6 +188,21 @@ module.exports = {
.setType('Number')
.setLabel('Shape Offset Y')
);
behaviorProperties.set(
'polygonOrigin',
new gd.PropertyDescriptor(behaviorContent.polygonOrigin || 'Center')
.setType('Choice')
.setLabel('Polygon Origin')
.addExtraInfo('Center')
.addExtraInfo('Origin')
.addExtraInfo('TopLeft')
);
behaviorProperties.set(
'vertices',
new gd.PropertyDescriptor(
JSON.stringify(behaviorContent.vertices || [])
).setLabel('Vertices')
);
behaviorProperties.set(
'density',
new gd.PropertyDescriptor(behaviorContent.density.toString(10))
Expand Down Expand Up @@ -232,38 +239,22 @@ module.exports = {
.setType('Number')
.setLabel('Gravity Scale')
);

// Waiting for a layers/masks editor
/*
// Transform the layers number into a binary string
var layers = behaviorContent.layers.toString(2);
// Reverse the string (so the first layer bit is shown at the left)
layers = layers
.split('')
.reverse()
.join('');
// Add zeros until the total size is 16
if (layers.length < 16) layers = layers + '0'.repeat(16 - layers.length);
// Expose the converted string
behaviorProperties.set(
'layers',
new gd.PropertyDescriptor(layers).setLabel('Layers')
new gd.PropertyDescriptor(behaviorContent.layers.toString(10))
.setType('Number')
.setLabel('Layers')
);
// Same than layers
var masks = behaviorContent.masks.toString(2);
masks = masks
.split('')
.reverse()
.join('');
if (masks.length < 16) masks = masks + '0'.repeat(16 - masks.length);
behaviorProperties.set(
'masks',
new gd.PropertyDescriptor(masks).setLabel('Masks')
new gd.PropertyDescriptor(behaviorContent.masks.toString(10))
.setType('Number')
.setLabel('Masks')
);
*/

return behaviorProperties;
};

physics2Behavior.setRawJSONContent(
JSON.stringify({
type: 'Dynamic',
Expand All @@ -275,6 +266,8 @@ module.exports = {
shapeDimensionB: 0,
shapeOffsetX: 0,
shapeOffsetY: 0,
polygonOrigin: 'Center',
vertices: [],
density: 1.0,
friction: 0.3,
restitution: 0.1,
Expand All @@ -293,23 +286,27 @@ module.exports = {
newValue
) {
if (propertyName === 'gravityX') {
sharedContent.gravityX = parseInt(newValue, 10);
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.gravityX = newValue;
return true;
}
if (propertyName === 'gravityY') {
sharedContent.gravityY = parseInt(newValue, 10);
newValue = parseFloat(newValue);
if (newValue !== newValue) return false;
behaviorContent.gravityY = newValue;
return true;
}
if (propertyName === 'scaleX') {
newValue = parseInt(newValue, 10);
if (newValue <= 0) newValue = 1;
sharedContent.scaleX = newValue;
if (newValue !== newValue) return false;
behaviorContent.scaleX = newValue;
return true;
}
if (propertyName === 'scaleY') {
newValue = parseInt(newValue, 10);
if (newValue <= 0) newValue = 1;
sharedContent.scaleY = newValue;
if (newValue !== newValue) return false;
behaviorContent.scaleY = newValue;
return true;
}

Expand Down
67 changes: 66 additions & 1 deletion Extensions/Physics2Behavior/physics2runtimebehavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ gdjs.Physics2RuntimeBehavior = function(runtimeScene, behaviorData, owner) {
this.shapeDimensionB = behaviorData.content.shapeDimensionB;
this.shapeOffsetX = behaviorData.content.shapeOffsetX;
this.shapeOffsetY = behaviorData.content.shapeOffsetY;
this.polygonOrigin = behaviorData.content.polygonOrigin;
this.polygon = this.getPolygon(behaviorData.content.vertices);
this.density = behaviorData.content.density;
this.friction = behaviorData.content.friction;
this.restitution = behaviorData.content.restitution;
Expand All @@ -227,6 +229,7 @@ gdjs.Physics2RuntimeBehavior = function(runtimeScene, behaviorData, owner) {
this._objectOldAngle = 0;
this._objectOldWidth = 0;
this._objectOldHeight = 0;
this._verticesBuffer = 0; // Stores a Box2D pointer of created vertices
};

gdjs.Physics2RuntimeBehavior.prototype = Object.create(
Expand All @@ -251,6 +254,11 @@ gdjs.Physics2RuntimeBehavior.prototype.onDeActivate = function() {
if (this._body !== null) {
// When a body is deleted, Box2D removes automatically its joints, leaving an invalid pointer in our joints list
this._sharedData.clearBodyJoints(this._body);
// Delete the vertices
if (this._verticesBuffer) {
Box2D._free(this._verticesBuffer);
this._verticesBuffer = 0;
}
// Delete the body
this._sharedData.world.DestroyBody(this._body);
this._body = null;
Expand All @@ -261,6 +269,15 @@ gdjs.Physics2RuntimeBehavior.prototype.ownerRemovedFromScene = function() {
this.onDeActivate();
};

gdjs.Physics2RuntimeBehavior.prototype.getPolygon = function(verticesData) {
var polygon = new gdjs.Polygon();
var maxVertices = 8;
for (var i = 0, len = verticesData.length; i < Math.min(len, maxVertices); i++) {
polygon.vertices.push([verticesData[i].x, verticesData[i].y]);
}
return polygon;
};

gdjs.Physics2RuntimeBehavior.prototype.createShape = function() {
// Get the scaled offset
var offsetX = this.shapeOffsetX
Expand Down Expand Up @@ -290,7 +307,55 @@ gdjs.Physics2RuntimeBehavior.prototype.createShape = function() {
// Set the offset
shape.set_m_p(this.b2Vec2(offsetX, offsetY));
} else if (this.shape === 'Polygon') {
// Needs a vertices editor
shape = new Box2D.b2PolygonShape();
// Not convex, fall back to a box
if (!this.polygon.isConvex()) {
var width =
(this.owner.getWidth() > 0 ? this.owner.getWidth() : 1) *
this._sharedData.invScaleX;
var height =
(this.owner.getHeight() > 0 ? this.owner.getHeight() : 1) *
this._sharedData.invScaleY;
// Set the shape box
shape.SetAsBox(width / 2, height / 2, this.b2Vec2(offsetX, offsetY), 0);
} else {
var originOffsetX = 0;
var originOffsetY = 0;
if(this.polygonOrigin === "Origin"){
originOffsetX = (this.owner.getWidth() > 0 ? -this.owner.getWidth()/2 : 0)
+ (this.owner.getX() - this.owner.getDrawableX());
originOffsetY = (this.owner.getHeight() > 0 ? -this.owner.getHeight()/2 : 0)
+ (this.owner.getY() - this.owner.getDrawableY());
}
else if(this.polygonOrigin === "TopLeft"){
originOffsetX = this.owner.getWidth() > 0 ? -this.owner.getWidth()/2 : 0;
originOffsetY = this.owner.getHeight() > 0 ? -this.owner.getHeight()/2 : 0;
}
// Generate vertices if not done already
if (!this._verticesBuffer) {
// Store the vertices using a memory allocation function
var buffer = Box2D._malloc(
this.polygon.vertices.length * 8,
'float',
Box2D.ALLOC_STACK
);
this._verticesBuffer = buffer;
}
// Overwrite the vertices stored in the buffer
var offset = 0;
for (var i = 0, len = this.polygon.vertices.length; i < len; i++) {
Box2D.HEAPF32[(this._verticesBuffer + offset) >> 2] =
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the >> 2? I also found it on kripken/box2d.js#87 but not sure exactly why it's useful when addressing the memory here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To divide by the size of float (4) and get the correct position, shift automatically deals with the decimal fraction.
It is a headache and would be a lot simpler if the support for setValue would not have been removed :(

(this.polygon.vertices[i][0] * this.shapeScale + originOffsetX) * this._sharedData.invScaleX +
offsetX;
Box2D.HEAPF32[(this._verticesBuffer + (offset + 4)) >> 2] =
(this.polygon.vertices[i][1] * this.shapeScale + originOffsetY) * this._sharedData.invScaleY +
offsetY;
offset += 8;
}
// Set the shape vertices
var b2Vertices = Box2D.wrapPointer(this._verticesBuffer, Box2D.b2Vec2);
shape.Set(b2Vertices, this.polygon.vertices.length);
}
} else if (this.shape === 'Edge') {
shape = new Box2D.b2EdgeShape();
// Length from the custom dimension or from the object width
Expand Down
20 changes: 20 additions & 0 deletions newIDE/app/src/BehaviorsEditor/BehaviorsEditorService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// @flow
import BehaviorPropertiesEditor from './Editors/BehaviorPropertiesEditor';
import Physics2Editor from './Editors/Physics2Editor';

/**
* A service returning editor components for each behavior type.
*/
export default {
getEditor(behaviorType: string) {
if (!this.components[behaviorType]) {
return BehaviorPropertiesEditor; // Default properties editor
}
return this.components[behaviorType].component; // Custom behavior editor
},
components: {
'Physics2::Physics2Behavior': {
component: Physics2Editor,
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {
type ResourceSource,
type ChooseResourceFunction,
} from '../../ResourcesList/ResourceSource.flow';
import { type ResourceExternalEditor } from '../../ResourcesList/ResourceExternalEditor.flow';

/**
* The props given to any behavior editor
*/
export type BehaviorEditorProps = {|
behavior: Object,
project: gdProject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
|};
Loading