Inkscape as a map editor

edited December 2012 in Projects - Tools
I've been thinking a bit about map/level editors, and it occurred that it might be a lot easier to parse the output of a vector drawing program than it would be to write my own UI map editor.

I wanted to have something that allowed for the simultaneous editing of the maps visual elements, and it's physics body elements at the same time.

Inkscape is pretty well suited for this sort of thing, and here's my take on it.

This is a very rough first version. Inkscape does some unusual things that can make the extraction of physics information a bit difficult. For example:

* Circles are NOT stored as an SVG <circle> element. They are stored as two semi-circular arc segments; we must detect if this is a proper circle. Sometimes (not sure what causes this) circles are stored as Bézier curves.

* Object transforms in parent elements can cause circle to be ... not circles, or make polygons degenerate (or wrong point order)

With the exception of the Bézier curves, I've addressed these problems.

Here's how it works:

In Inkscape:

Create a new layer called "Physics". Objects in this layer will be converted to simple static body parts. The same restrictions that ORX imposes on physics o
bjects are in effect:
1) No curves, except circles
2) No concave polygons
3) No polygons with more than 8 vertices

There is a 4th rule: "No counter-clockwise polygons," but you needn't worry about it; this problem is silently fixed by the conversion tool.

All other layers are _ignored_.

The script:

Reads an SVG file, and outputs:
* A PNG image of the SVG (with 'Physics' layer suppressed)
* An ORX config .ini file containing all the objects in the 'Physics' layer.

What it _doesn't_ do:
* Add any of the extended body part or body settings (e.g. Friction, Restitution, SelfFlags, CheckMask, etc)

Here's the generated .ini. Screenshots in replies.
[bodyPart.4e813e18-a7c2-4c18-b7a7-5b9f3af15e53]
  Type = mesh
  VertexList = {124.231450, 75.170090, 0.0} # {576.579200, 75.170090, 0.0} # {576.579200, 277.863830, 0.0} # {124.231450, 277.863830, 0.0}

[body.4e813e18-a7c2-4c18-b7a7-5b9f3af15e53]
  PartList = bodyPart.4e813e18-a7c2-4c18-b7a7-5b9f3af15e53

[object.4e813e18-a7c2-4c18-b7a7-5b9f3af15e53]
  Body = body.4e813e18-a7c2-4c18-b7a7-5b9f3af15e53
  ; src: ../main/svg/maptest1.svg:491 id="rect5332"

[bodyPart.b09bc9b2-8d72-4aec-9188-5bc6b951c3ce]
  Type = mesh
  VertexList = {25.383045, 9.799792, 0.0} # {134.058597, 43.075419, 0.0} # {114.404307, 107.264767, 0.0} # {5.728754, 73.989140, 0.0}

[body.b09bc9b2-8d72-4aec-9188-5bc6b951c3ce]
  PartList = bodyPart.b09bc9b2-8d72-4aec-9188-5bc6b951c3ce

[object.b09bc9b2-8d72-4aec-9188-5bc6b951c3ce]
  Body = body.b09bc9b2-8d72-4aec-9188-5bc6b951c3ce
  ; src: ../main/svg/maptest1.svg:500 id="rect3762"

[bodyPart.a12e53e1-70e8-4579-bd7a-bfed368f8cca]
  Type = mesh
  VertexList = {134.058592, 43.075423, 0.0} # {211.838091, 125.948776, 0.0} # {162.888844, 171.889332, 0.0} # {85.109345, 89.015979, 0.0}

[body.a12e53e1-70e8-4579-bd7a-bfed368f8cca]
  PartList = bodyPart.a12e53e1-70e8-4579-bd7a-bfed368f8cca

[object.a12e53e1-70e8-4579-bd7a-bfed368f8cca]
  Body = body.a12e53e1-70e8-4579-bd7a-bfed368f8cca
  ; src: ../main/svg/maptest1.svg:510 id="rect3762-7"

[bodyPart.25f21270-db42-490c-ba90-0fa1a82aa59a]
  Type = mesh
  VertexList = {211.838060, 125.948760, 0.0} # {211.838060, 239.604570, 0.0} # {144.707117, 239.604570, 0.0} # {144.707117, 125.948760, 0.0}

[body.25f21270-db42-490c-ba90-0fa1a82aa59a]
  PartList = bodyPart.25f21270-db42-490c-ba90-0fa1a82aa59a

[object.25f21270-db42-490c-ba90-0fa1a82aa59a]
  Body = body.25f21270-db42-490c-ba90-0fa1a82aa59a
  ; src: ../main/svg/maptest1.svg:520 id="rect3762-7-8"

[bodyPart.798d435a-c135-4fab-8043-473e82287ab1]
  Type = mesh
  VertexList = {178.272600, 239.604610, 0.0} # {525.788900, 239.604610, 0.0} # {525.788900, 306.735553, 0.0} # {178.272600, 306.735553, 0.0}

[body.798d435a-c135-4fab-8043-473e82287ab1]
  PartList = bodyPart.798d435a-c135-4fab-8043-473e82287ab1

[object.798d435a-c135-4fab-8043-473e82287ab1]
  Body = body.798d435a-c135-4fab-8043-473e82287ab1
  ; src: ../main/svg/maptest1.svg:529 id="rect3762-7-8-2"

[bodyPart.d2663f67-289a-4d08-80cf-52902a5cf5a5]
  Type = mesh
  VertexList = {698.332717, 73.989138, 0.0} # {589.657165, 107.264766, 0.0} # {570.002875, 43.075418, 0.0} # {678.678427, 9.799791, 0.0}

[body.d2663f67-289a-4d08-80cf-52902a5cf5a5]
  PartList = bodyPart.d2663f67-289a-4d08-80cf-52902a5cf5a5

[object.d2663f67-289a-4d08-80cf-52902a5cf5a5]
  Body = body.d2663f67-289a-4d08-80cf-52902a5cf5a5
  ; src: ../main/svg/maptest1.svg:539 id="rect3762-6"

[bodyPart.31d828b9-9425-4d8c-919e-417b2da9028a]
  Type = mesh
  VertexList = {618.952141, 89.015988, 0.0} # {541.172642, 171.889342, 0.0} # {492.223394, 125.948785, 0.0} # {570.002893, 43.075432, 0.0}

[body.31d828b9-9425-4d8c-919e-417b2da9028a]
  PartList = bodyPart.31d828b9-9425-4d8c-919e-417b2da9028a

[object.31d828b9-9425-4d8c-919e-417b2da9028a]
  Body = body.31d828b9-9425-4d8c-919e-417b2da9028a
  ; src: ../main/svg/maptest1.svg:549 id="rect3762-7-3"

[bodyPart.5127354d-f255-4224-bc88-5d62acc143d3]
  Type = mesh
  VertexList = {559.354383, 125.948760, 0.0} # {559.354383, 239.604570, 0.0} # {492.223440, 239.604570, 0.0} # {492.223440, 125.948760, 0.0}

[body.5127354d-f255-4224-bc88-5d62acc143d3]
  PartList = bodyPart.5127354d-f255-4224-bc88-5d62acc143d3

[object.5127354d-f255-4224-bc88-5d62acc143d3]
  Body = body.5127354d-f255-4224-bc88-5d62acc143d3
  ; src: ../main/svg/maptest1.svg:559 id="rect3762-7-8-4"

[bodyPart.ac562ac2-de1f-48bd-beb8-918859da8016]
  Type = mesh
  VertexList = {393.461250, 64.480000, 0.0} # {423.439100, 79.468920, 0.0} # {411.447960, 107.198420, 0.0} # {381.470110, 113.943440, 0.0} # {362.733950, 90.710610, 0.0}

[body.ac562ac2-de1f-48bd-beb8-918859da8016]
  PartList = bodyPart.ac562ac2-de1f-48bd-beb8-918859da8016

[object.ac562ac2-de1f-48bd-beb8-918859da8016]
  Body = body.ac562ac2-de1f-48bd-beb8-918859da8016
  ; src: ../main/svg/maptest1.svg:564 id="path3296"

[bodyPart.f11807ef-275d-4920-b5d6-7a8d3a971611]
  Type = mesh
  VertexList = {306.573000, 60.409830, 0.0} # {293.832420, 36.427550, 0.0} # {318.564140, 18.440840, 0.0} # {349.291440, 19.939730, 0.0} # {361.282580, 40.924230, 0.0} # {352.289230, 56.662600, 0.0}

[body.f11807ef-275d-4920-b5d6-7a8d3a971611]
  PartList = bodyPart.f11807ef-275d-4920-b5d6-7a8d3a971611

[object.f11807ef-275d-4920-b5d6-7a8d3a971611]
  Body = body.f11807ef-275d-4920-b5d6-7a8d3a971611
  ; src: ../main/svg/maptest1.svg:569 id="path3298"

[bodyPart.cadfa481-c2f2-42d2-be6f-563fa0dbd9d3]
  Type = mesh
  VertexList = {411.859570, 60.914490, 0.0} # {404.367420, 64.073260, 0.0} # {393.224660, 62.999040, 0.0} # {376.435620, 47.365350, 0.0} # {372.253980, 19.952950, 0.0} # {385.376240, 6.579470, 0.0} # {418.577570, 20.467670, 0.0}

[body.cadfa481-c2f2-42d2-be6f-563fa0dbd9d3]
  PartList = bodyPart.cadfa481-c2f2-42d2-be6f-563fa0dbd9d3

[object.cadfa481-c2f2-42d2-be6f-563fa0dbd9d3]
  Body = body.cadfa481-c2f2-42d2-be6f-563fa0dbd9d3
  ; src: ../main/svg/maptest1.svg:577 id="path3300"

[bodyPart.b3aabc56-fe3d-47c1-b9b7-59f0311b5fc9]
  Type = mesh
  VertexList = {0.500000, 0.500000, 0.0} # {17.993832, 0.500000, 0.0} # {17.993832, 17.993832, 0.0} # {0.500000, 17.993832, 0.0}

[body.b3aabc56-fe3d-47c1-b9b7-59f0311b5fc9]
  PartList = bodyPart.b3aabc56-fe3d-47c1-b9b7-59f0311b5fc9

[object.b3aabc56-fe3d-47c1-b9b7-59f0311b5fc9]
  Body = body.b3aabc56-fe3d-47c1-b9b7-59f0311b5fc9
  ; src: ../main/svg/maptest1.svg:584 id="rect4115"

[bodyPart.d8ead240-9c5f-4d66-87d9-b63ef8f9541c]
  Type = sphere
  Center = {305.939880, 23.149834, 0.0}
  Radius = 11.178744

[body.d8ead240-9c5f-4d66-87d9-b63ef8f9541c]
  PartList = bodyPart.d8ead240-9c5f-4d66-87d9-b63ef8f9541c

[object.d8ead240-9c5f-4d66-87d9-b63ef8f9541c]
  Body = body.d8ead240-9c5f-4d66-87d9-b63ef8f9541c
  ; src: ../main/svg/maptest1.svg:594 id="path4135"

[object.map.physics]
  ChildList = object.4e813e18-a7c2-4c18-b7a7-5b9f3af15e53 # object.b09bc9b2-8d72-4aec-9188-5bc6b951c3ce # object.a12e53e1-70e8-4579-bd7a-bfed368f8cca # object.25f21270-db42-490c-ba90-0fa1a82aa59a # object.798d435a-c135-4fab-8043-473e82287ab1 # object.d2663f67-289a-4d08-80cf-52902a5cf5a5 # object.31d828b9-9425-4d8c-919e-417b2da9028a # object.5127354d-f255-4224-bc88-5d62acc143d3 # object.ac562ac2-de1f-48bd-beb8-918859da8016 # object.f11807ef-275d-4920-b5d6-7a8d3a971611 # object.cadfa481-c2f2-42d2-be6f-563fa0dbd9d3 # object.b3aabc56-fe3d-47c1-b9b7-59f0311b5fc9 # object.d8ead240-9c5f-4d66-87d9-b63ef8f9541c

Comments

  • edited December 2012
    Screenshot of Inkscape showing only the graphics layers:
    graphicsOnly.png
  • edited December 2012
    Screenshot with only physics layer:
    physicsOnly.png
  • edited December 2012
    Combined layers:
    combined.png
    combined 129.6K
  • edited December 2012
    (clipped) screenshot from my ORX executable. The 'missle' and 'beachball' graphics are created by the executable from different INI files.

    I didn't load any of the graphics layers (nor is the PNG created referenced by the created INI), so all you see is the physics debug outlines. The large square in the middle is intended to be a 'sensor' type block, eventually:

    screenshot.png
  • edited December 2012
    Source code: https://forum.orx-project.org/uploads/legacy/fbfiles/files/svg2map.gz

    Usage: svg2map.py <infile1.svg> <infile2.svg> <outDir>
    svg2map 18.7K
  • edited December 2012
    First of all, congrats for all the great work! :)

    That looks really neat! However I have a couple of questions for you:

    - Do you really need to use (what looks like) UUIDs in the config section names? Wouldn't a string made-up from the svg file name + the object id be enough? (ie. maptest1-path3296, for example)
    - Can more than one body part be defined for a body? If not, you could collapse both sections in one for more readability. Even if you can't collapse body and bodypart, object and body should still be mergeable (using the property Body = @ to self-refer).

    Lastly, I've added you to the orx team on bitbucket if you want to add your converter to all the tools that are already proposed there: https://bitbucket.org/orx

    Congrats again, I'm sure that will be useful to a bunch of people!
  • edited December 2012
    - Do you really need to use (what looks like) UUIDs in the config section names? Wouldn't a string made-up from the svg file name + the object id be enough? (ie. maptest1-path3296, for example)

    Uuid was simply convenient at the time. It will be trivial to implement it as you have described.
    - Can more than one body part be defined for a body? If not, you could collapse both sections in one for more readability. Even if you can't collapse body and bodypart, object and body should still be mergeable (using the property Body = @ to self-refer).

    Another good idea. I'll implement this next. I just wanted a proof of concept working before I went to bed last night.
    Lastly, I've added you to the orx team on bitbucket if you want to add your converter to all the tools that are already proposed there: bitbucket.org/orx

    Cool; I'll check it out.
  • edited December 2012
    And it's a great proof of concept! :)

    When it's on bitbucket I can post a news on the site, if that's ok with you.
  • edited December 2012
    Fine by me. Be sure to note it's still Alpha level code.

    It's here: https://bitbucket.org/epoulsen/svg2map
  • edited January 2013
    Hi!

    I'm afraid your repository is private, I can't access it.

    If you move it to the orx team, you'll still be the creator of the repo and will still be able to decide the access rights there.
  • edited January 2013
    Sorry, I'm not super familiar with Bitbucket et. al. I didn't see this message until just now.

    [strike]I didn't see a way to move it to ORX, except to fork it, which I have done.[/strike]

    Never mind, I just re-created it and re-pushed the initial checkin.
  • edited January 2013
    I added some new stuff.

    I originally played around with having the script be able to 'convert' svg files to add some custom namepaces (e.g. orx:)

    Unfortunately, Inkscape throws away root element namespaces.

    However, Inkscape doesn't seem to care about custom attributes, so here's what I've implemented:
    <!-- This lets you set a custom section name
          for this physics object.  Useful for sensors -->
    <blah  orx-section-name="mango" .../>
    
    <!-- Any other attribute that begins with orx- will
         become a  name-value pair in the
         resulting config section -->
    <blah  orx-anyname="anyvalue" .../>
    <!-- example: -->
    <blah  orx-Solid="false" .../>
    

    Also, I've implemented all of the changes you mentioned, as much as I can.

    Physics objects generated by the script will have a default Solid=true attribute; this is the only default that goes against the ORX default. Mostly because it suits my purposes :)

    The attached (cropped) screenshot is the map loaded by my test ORX executable, with physics debugging turned on. The largest physics rectangle has Solid set to 'false' for use as an area sensor. The loaded .ini file is the unmodified file from the script.

    Here's some of the end of the .ini file. I've added the graphic section, and a 'meta' section whose name is hard-coded (though this will probably be a command line option of the script soon).
    [body.maptest1]
      PartList = WaterSensor # bodyPartrect3762 # bodyPartrect3762-7 # bodyPartrect3762-7-8 # bodyPartrect3762-7-8-2 # bodyPartrect3762-6 # bodyPartrect3762-7-3 # bodyPartrect3762-7-8-4 # bodyPartpath3296 # bodyPartpath3298 # bodyPartpath3300 # WaterSensor.1
    
    [graphic.maptest1]
      Texture = maptest1.png
    
    [object.maptest1]
      Body = body.maptest1
      Graphic = graphic.maptest1
    
    [object.map.meta]
      ObjectName = object.maptest1
    
    with_real_graphics.png
  • edited January 2013
    Thanks, works just fine!

    For future reference though, when you click on the gear icon on your repository page (top right), it brings you to the settings page.
    There you can find a "transfer repository" tab that will let you transferring the repository to a different account.
  • edited January 2013
    Nice work, thanks for the update!

    By the way, how do you set the collision flags/masks of parts, is it via the custom attribute system you mentioned?
  • edited January 2013
    Ah, good question.

    I defaulted the script to use:

    CheckMask = 0xFFFF
    SelfFlags = 0x8000

    There's no reason you cannot override this by doing:

    <svgelem orx-CheckMask="0xEEEE" orx-SelfFlags="0xDDDD" />

    Probably many of these should be defaulted by command-line switches in the script. Or maybe root-element defaults. Or maybe both. =)
Sign In or Register to comment.