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
Merged

Conversation

Lizard-13
Copy link
Contributor

Also adds custom polygon support.

@4ian Now the parser works fine, I can make a fast test and check that un/serializing an isolated behavior content string convert the string to JSON and to string again correctly, arrays included. But for some reason, it doesn't work when opening a project file, I'm doing the following:

  • Create a new project
  • Add the physics behavior, the default values for vertices is []
  • Save the project >> The array is correctly saved in the game file
  • Open the project >> The array is automatically converted to an empty object {}

Again, the parser works fine with the same isolated behavior string, am I going crazy?

@@ -27,11 +28,15 @@ export default (
.getExtraInfo()
.toJSArray()
.map(value => ({ value, label: value }));
const dialog = PropertiesRenderingService.getDialogComponent(
property.getExtraInfo().toJSArray()[0]
Copy link
Owner

Choose a reason for hiding this comment

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

This looks risky to me. [0] could return undefined and you typed getDialogComponent as taking a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, yeah, I know, I feel a bit bad each time I read that "line", very WIP maybe?
I've to add some extra safe checks.

Copy link
Owner

Choose a reason for hiding this comment

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

Maybe you could actually move this into the getDialog function? Would defer the call to getDialogComponent until it's really needed - so no useless call and easier to debug calls to getDialogComponent too

@4ian
Copy link
Owner

4ian commented Dec 24, 2018

You're 100% sure you're running the version of libGD.js with the fix right?

@4ian
Copy link
Owner

4ian commented Dec 24, 2018

I'm adding the behavior demo that you created (test beds for joints) and when converting it for the webapp, the whole behavior is not saved, so there is some issue somewhere 🤔

@4ian
Copy link
Owner

4ian commented Dec 24, 2018

Mmm no sorry it was another issue (forgot to load extensions in the script that create the examples files for the web-app).

@Lizard-13
Copy link
Contributor Author

What would be the best way to show an image preview in the behaviors editor? passing the resources manager information to the BehaviorsEditor here:

<BehaviorsEditor
object={this.props.object}
project={this.props.project}
onSizeUpdated={
() =>
this.forceUpdate() /*Force update to ensure dialog is properly positionned*/
}
/>

Sounds fine, it's done for the object properties some lines before, but then passing the resource to the PropertiesEditor looks weird:
<PropertiesEditor
schema={propertiesSchema}
instances={[behavior]}
/>

What do you think?

@4ian
Copy link
Owner

4ian commented Dec 26, 2018

Sorry for the delay, mmm I guess we can start by passing resourceSources down to the PropertiesEditor. Make it an optional Prop (so that it's not mandatory for other property editors). Will see if there is a better way, but I don't think so (when thinking about it, if a PropertiesEditor has to shown fields that need the resources... then it needs to have the resources).
Another solution could be to have a "prop" that is passed to the properties editor which is an object containing functions for rendering new kind of fields. But for now let's just modify properties editor to have the resources, I think it's not a big deal to pass it :)

* Dynamic dimension labels in function of the shape
* Preview box, circle and edge
* Prevent invalid properties values
@Lizard-13
Copy link
Contributor Author

Lizard-13 commented Dec 28, 2018

Now it previews almost every shape:
shapepreview
The polygon editor/preview is still missing. Unless I'm forgetting something, this and investigate the bad vertices parsing is the only and last step to do.

EDIT: There is a small problem but not lethal, shapes preview are restricted to the image origin, if you move the shape to the left or up (through the offset) the image will be truncated.

@zatsme
Copy link
Contributor

zatsme commented Dec 29, 2018

Just a thought, are you adding a zoom to this editor so it's not like the existing hitbox one and if you are, will this code be usable there?


type Props = {|
onInstancesModified?: Instances => void,
instances: Instances,
schema: Schema,
mode?: 'column' | 'row',
project?: Project,
Copy link
Owner

Choose a reason for hiding this comment

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

use gdProject :) Typing for objects of libGD.js is done inside flow-typed/libGD.js file. Most of the definitions right now are very simple, but it's important to always use the proper type, because in the future we might want to write them properly or autogenerate them from C++ classes.

render() {
const { mode } = this.props;

for (var i = 0, len = this.props.schema.length; i < len; i++) {
if (this.props.schema[i].getDialog) {
return (
Copy link
Owner

@4ian 4ian Dec 29, 2018

Choose a reason for hiding this comment

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

I'm confused about this part. Shouldn't that be done below, for each field? (by below I mean, after if (field.getValue) something like this: if (field.getDialog) return this._renderDialogField(field);)
I was thinking of getDialog as something that is specific for a field, not something that would overwrite all the properties editor - in this case, better don't even have a property editor in the first place.
Do you think it would be possible to instead display the content of the "dialog" inline with other fields? That would reduce the need to create custom dialogs for only the fields that need it.
Otherwise, it looks weird to me to have a field having a "getDialog", that will "hijack" the whole editor for all the rest of the field.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But how to make it to not render fields defined in the custom dialog?
For example if I render everything it will render my dialog and the density property, and I need the density property defined to use it in the dialog... or an I missing something?

Copy link
Owner

Choose a reason for hiding this comment

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

Let me think about it, but I think we can move toward a solution that is more similar to ObjectsEditorService, but for Behaviors (BehaviorsEditorService). i.e: we register the editor that you created for the behavior. If a behavior does not have a registered editor, then BehaviorsEditorService will create a PropertiesEditor, like what we do now.

I was thinking of doing the same for ObjectsEditorService: if you don't set an editor for your object, a default editor showing the properties will be displayed.
So both ObjectsEditorService and BehaviorsEditorService would work the same. It would avoid having a strange handling of "getDialog" in PropertiesEditor, allow custom editors while providing a default editor for most behaviors/objects.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice!, will read the ObjectsEditorService in-depth.

@Lizard-13
Copy link
Contributor Author

Lizard-13 commented Dec 31, 2018

The polygon editor is up:
custompolygon

Things to check out:

  • Loading vertices from game files don't work, you can try it out, edit a polygon, save, and open the file in a text editor (you should see the vertices are correctly saved, previews work too), reopen the project in GD and in the behavior editor the vertices raw data (a debug field) will show an empty array (I've added it here to avoid crashes:
    // Parsing error temporary workaround
    if (
    !Array.isArray(
    JSON.parse(
    behavior
    .getProperties()
    .get('vertices')
    .getValue()
    )
    )
    ) {
    behavior.updateProperty('vertices', '[]', project);
    }
  • Discuss and improve the way to render the dialog and other properties
  • I've translated the C++ code to check polygon convexity to use it from the IDE, I can switch to create a gd.Polygon2d in the fly if you want, although it would require to create gd.Vector2f's each time.
  • There are warnings about mixing ">" and "!==", it's easy to fix adding a parenthesis, but pretiffy doesn't like the idea and removes them.
  • I'm not taking theming into account, AFAIK nothing is themable, will check it out later.
  • And, of course, I'm a total noob with react so the design could be totally wrong :D

btw the wiki article is growing (http://wiki.compilgames.net/doku.php/gdevelop5/behaviors/physics2), will start adding images and instructions once the editor is completed :)

@Lizard-13
Copy link
Contributor Author

Forget the theme, @4ian you've done it so well that everything is already themable
darktheme

@zatsme I'm using the same image preview than the sprite collision mask editor, so no zoom right now :(

@4ian
Copy link
Owner

4ian commented Dec 31, 2018

This is incredible work! ❤️ I'll take a look at this as soon as possible, thanks for listing the things to check out.

I'm using the same image preview than the sprite collision mask editor, so no zoom right now :(

Yep, zoom should be something that should be added to work with anything showing a preview. I think it's outside of the scope of the extension.

@4ian you've done it so well that everything is already themable

That looks great! Yeah the goal is that any component that you use for building the UI is already themed. Glad it worked without you doing anything special :)

the wiki article is growing (http://wiki.compilgames.net/doku.php/gdevelop5/behaviors/physics2), will start adding images and instructions once the editor is completed :)

Great! The wiki article together with this full featured extensions and editor will be super appreciated by all users!

@zatsme
Copy link
Contributor

zatsme commented Dec 31, 2018

Great that the zoom code when added will work for all 😁

4ian
4ian previously approved these changes Dec 31, 2018
/>
</Line>
<Line>
<div style={{ width: '50%' }}>
Copy link
Owner

Choose a reason for hiding this comment

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

I think you can use two <Column noMargin> instead of div with width 50%

this.forceUpdate();
}}
>
{[
Copy link
Owner

Choose a reason for hiding this comment

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

nit: the array seems unnecessary ( {[ ... ]} )

</SelectField>
</Line>
<Line>
<div style={{ width: '33%' }}>
Copy link
Owner

Choose a reason for hiding this comment

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

Prefer using <Column noMargin> rather than div


renderBitProperty(
properties: Object,
isLayer: boolean,
Copy link
Owner

Choose a reason for hiding this comment

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

Rather than passing isLayer, it would be a bit more extensible if you pass "propertyName" :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, don't know why I did it this way... :P

@4ian
Copy link
Owner

4ian commented Dec 31, 2018

(don't care about the approve thing, I just wanted to comment ^^)

@Lizard-13
Copy link
Contributor Author

I'm happy you've not said I'm using the properties and states all upside down :)
Don't worry, more than happy to improve it.

@4ian
Copy link
Owner

4ian commented Dec 31, 2018

No, most things make sense :) Will have to check more thoroughly because I only did a quick pass so expect more comments but we're going in the right direction, especially once we have the BehaviorsEditorService! :)

import AddCircle from 'material-ui/svg-icons/content/add-circle';
import Delete from 'material-ui/svg-icons/action/delete';

type Vertex = {
Copy link
Owner

Choose a reason for hiding this comment

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

Can you use exact object typing here? ( "burger" {| ... |} notation )

Copy link
Owner

Choose a reason for hiding this comment

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

Also do export type Vertex = ... to import it in another file.

// @flow
import * as React from 'react';

type Vertex = {
Copy link
Owner

Choose a reason for hiding this comment

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

Better do import { type Vertex } from './PolygonEditor'

const { draggedVertex } = this.state;
if (!draggedVertex) return;

const pointOnScreen = this._svg.createSVGPoint();
Copy link
Owner

Choose a reason for hiding this comment

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

I recognize this code ;) I think at some point we'll have to factor this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I see what you did there, coordinate systems between different elements is a pain :/


this.state = {
image: '',
imageWidth: 9,
Copy link
Owner

Choose a reason for hiding this comment

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

Any reason for this 9 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.

Haha, that's just a very sexy 0 :D

});
};

_isPolygonConvex(properties: Object) {
Copy link
Owner

@4ian 4ian Dec 31, 2018

Choose a reason for hiding this comment

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

nitpicking: properties is a gdMapStringPropertyDescriptor (even if this map to Object for now).
I hope to be able to bring better typing to GDevelop.js later, I've seen some projects like nbind creating typing automatically.

);
}

renderNumericProperty(
Copy link
Owner

@4ian 4ian Dec 31, 2018

Choose a reason for hiding this comment

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

Note that instead of doing a renderXXX method, you could extract this function out of the component, call it NumericProperty, and have an object as argument. Let's call this argument... props :)

function NumericProperty(props: {| properties: Object, name: string, limit: boolean, min: number, step: number |})

And boom, you have a component :) Well actually, you need to pass project and behavior and a "onUpdate" callback. Then you can do in your render:

<NumericProperty 
     properties={properties}
     name={...}
     limit
     min={...}
     step={...}
     onUpdate={...}
/>

(btw, this is what is super great with React. The cost of abstracting something in a component is literally to move a function... this make the barrier to refactoring and abstracting things ultra low).

It's a bit more readable that using renderXXX methods. It also allow to move these extracted component in other files if needed (here it's fine to keep them in a single file, colocating components is a good thing as long as they are not too big).

@Lizard-13
Copy link
Contributor Author

Applied the suggested changes, "expand" in the columns did the trick. Also moved the polygon convexity check to the polygon editor, has a lot more sense to be there, and added the limit to 8 vertices.

constructor(props: Props) {
super(props);

this.resourcesLoader = ResourcesLoader;
Copy link
Owner

Choose a reason for hiding this comment

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

In fact, I think we can entirely avoid a constructor by doing:

      resourcesLoader = ResourcesLoader;
 
      state = {
       image: '',
       imageWidth: 0,
       imageHeight: 0,
     };

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Haha, something I would love to change, readed the constructor was the recommend way when you had default state values, but I trust you :)

)}
</Line>
<Line>
<SemiControlledTextField // Debug vertices raw content
Copy link
Owner

Choose a reason for hiding this comment

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

I guess this is to be removed once we've fixed the issue with vertices?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, this and the vertices array check/reset.

</EmptyMessage>
)}
</div>
<BehaviorComponent
Copy link
Owner

Choose a reason for hiding this comment

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

Looks great 👍

@4ian
Copy link
Owner

4ian commented Jan 3, 2019

Can you explain me again the issue with the vertices loading? I'll try to reproduce the issue and fix it

);

return (
<div style={styles.propertiesContainer}>
Copy link
Owner

@4ian 4ian Jan 3, 2019

Choose a reason for hiding this comment

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

I've copied this editor almost exactly for the future default editor for objects 👍
Note that we can use <Column> instead of the div and remove the custom style propertiesContainer . This will use the margins from Column which are standardized across the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will change it then :)

Copy link
Owner

Choose a reason for hiding this comment

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

@Lizard-13
Copy link
Contributor Author

Lizard-13 commented Jan 3, 2019

The vertices array is not loaded properly from the game file (initial game parsing). Try it: edit an object with a polygon and save, if you edit the game file you'll notice the vertices are properly saved. Open the game again in GD and the vertices array is converted to a degenerated object instantly.

The weird part is that I've un/serialized the behavior content in a hardcoded string and the serializer loads the vertices correctly.

@Lizard-13
Copy link
Contributor Author

Seems that the only missing points from my original list are the bad vertices loading and the warnings about mixing ">" and "!==" without parenthesis removed by prettify.

@4ian
Copy link
Owner

4ian commented Jan 4, 2019

Ok, for the warnings about mixing operators, put zCrossProduct > 0 in a boolean zCrossProductIsPositive and compare against it: zCrossProductIsPositive !== zProductIsPositive. That will force us to be a bit more explicit ;)

@4ian
Copy link
Owner

4ian commented Jan 4, 2019

Found the culprit for the vertices! Turns out that this was the issue: 4ian/GDevelop.js@7feb8d3#diff-0b0a8f42de3a19264369288a15d8b56bR133

So in JavaScript, with GDevelop.js, it's more efficient to let JavaScript parse JSON (using JSON.parse) and then traverse this object to create the associated gd.SerializerElement (that can feed into GDevelop.js) instead of using gd.Serializer.fromJSON (which does the parsing using the C++ written JSON serializer) (JSON.parse was super optimized by browsers vendors and is running as machine code)

So what I did was to add an adhoc "special" function called gd.Serializer.fromJSObject. This is the function doing the step I was talking about "traverse this object to create the associated gd.SerializerElement".
(And it's also easier in JavaScript to store a JS object rather than converting everything back and forth to JSON, for example for the undo/redo history. With gd.Serializer.fromJSObject, we can avoid converting everything to JSON)

The issue was that I properly fixed earlier gd.Serializer.fromJSON to understand arrays and store them as SerializerElement that are arrays, but I totally forgot to update gd.Serializer.fromJSObject.
I've fixed this and update libGD.js

I was able to save a polygon, open it again, modify it, save it and open it again. Ideally, I will add a test case for this in the tests of GDevelop.js.

@Lizard-13
Copy link
Contributor Author

Lizard-13 commented Jan 4, 2019

I've opened a pull request some hours ago in GDevelop.js, hehe I won!

@4ian
Copy link
Owner

4ian commented Jan 4, 2019

Oh sorry I missed the pull request (but I've made sure to be notified of any future one). Good job, you won indeed, here is your medal: 🥇;)
So we should be very close to be ready here. Triple check that everything is working and I'll merge this.

@Lizard-13
Copy link
Contributor Author

Yeah, let me do some save/loads and polygon tests

@Lizard-13
Copy link
Contributor Author

Thanks for the medal!
lizardinwon

@Lizard-13
Copy link
Contributor Author

Creating and deleting instances (it erases manually allocated memory, so it's very important):
multipleinstances

Resizing shape from different origins, note the concave polygon falls back to a box and is not affected by shape scale:
resizeshape

@Lizard-13
Copy link
Contributor Author

I've noticed that the polygon editor renders the top-left polygon origin in the same way than from the origin point, not sure if it's possible to fix?, can you access the object origin point from the IDE?

@4ian
Copy link
Owner

4ian commented Jan 4, 2019

I've noticed that the polygon editor renders the top-left polygon origin in the same way than from the origin point, not sure if it's possible to fix?, can you access the object origin point from the IDE?

I don't think there is anything like this. By the way, ideally we would automatically load a preview of the object rather than asking to the user to select an image, which is I think a bit strange/counter intuitive?
For now what we have to render objects are the renderers for instances, for the scene editor. That would need to have them in some way being able to be displayed in a pixi.js canvas, behind the polygon (doable, but would need quite a bit of refactoring to do this properly - but doable)

@Lizard-13
Copy link
Contributor Author

Yeah, that would require lot of work, as it has to be abstracted from the object type, for example should the preview render a text object (using the default string)?

A nice future goal, make the image preview not truncated at the image topleft, support zoom, image origin and maybe a coordinates converter. And add an object renderer that can make a render of an object into a texture, and pass this texture and the object origin to the image preview.

@4ian
Copy link
Owner

4ian commented Jan 4, 2019

Yeah, that would require lot of work, as it has to be abstracted from the object type, for example should the preview render a text object (using the default string)?

I think ideally it should work by creating a Pixi renderer (instead of the image) and then rendering the instance (the renderers extending RenderedInstance) like it's done in the scene editor (ObjectsRenderingService.createNewInstanceRenderer).
So that you'll get the exact object as rendered on the scene editor (of course you can't resize it or modify the angle, but that's the point). And you get for free the rendering of all object types already done for you :) (and people writing objects only have one renderer to write for the IDE)

@4ian
Copy link
Owner

4ian commented Jan 5, 2019

Will merge now :)

@4ian 4ian merged commit 3fc588b into 4ian:master Jan 5, 2019
@4ian
Copy link
Owner

4ian commented Jan 5, 2019

Huge huge thanks @Lizard-13 for working on this! This new editor will be something that will definitely help people take advantage of the new physics engine!!

As next steps, we should I think concentrate on having a few examples and a exhaustive wiki page.
Ideally, we should remove all the "old" examples or rework them to use the Physics2 engine :)

@4ian
Copy link
Owner

4ian commented Jan 5, 2019

Though it seemed that I've catched a bug: I opened a demo (physics-joints-demo.json) and tried to set a polygon for RevoluteBox, but when lauching the game the vertices are not defined:
image

@Lizard-13
Copy link
Contributor Author

Will take a look right now.

@Lizard-13 Lizard-13 deleted the physics2-behavior-editor branch January 5, 2019 15:46
@Lizard-13
Copy link
Contributor Author

Fixed, polygons work perfect, the problem was on not-polygon shapes :/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants