Object Coordinate Transformation Script

Interesting mapping tools and mapping help.

Moderator: Morax

Object Coordinate Transformation Script

Postby Lionhardt » 26 Jul 2015, 01:42

Because the editor lacks any such tool I made a script that does the job for me. Its quick and dirty. But it "just works".


Okay, so you need python to run this and it does the following:


translate.jpg
translate.jpg (26.69 KiB) Viewed 6384 times


project.jpg
project.jpg (26.02 KiB) Viewed 6384 times


resize.jpg
resize.jpg (27.87 KiB) Viewed 6384 times



Transforming object coordinates is required when porting and resizing maps.

Here's the code:


Code: Select all
# Quick and dirty hacky script for transforming the coordinates of decals,
# props, markers and units.
# Absolute values are used, because the input format is fixed anyway.
# This script can be destructive, if handled incorrectly. Never operate
# on original map files!
# Also note, that importing decals and props will ADD props at the new
# positions. Old props and decals will persit until deleted manually.
# To transform marker unit positions, pass in the <map>_save.lua
# To transform prop positions, pass in the props file (export from editor)
# To transform decals positions, pass in the decals file (export from editor)
# version: 1, author: Lionhardt, date: 25.07.2015

from sys import argv
import sys
from math import sin
from math import cos
from math import radians

########################################################################
# translation sub-functions and objects
########################################################################
# function object for translation
class Translation:
   def __init__(self, x_index, y_index, x_offset, y_offset):
      self.x_index = x_index
      self.y_index = y_index
      self.x_offset = x_offset
      self.y_offset = y_offset
########################################################################
def _trans(coord, offset):
   return str(float(coord)+offset)
########################################################################
def _translate(string, offset):
   if string[-1] == ",":
      return _trans(_isolate(string), offset)+","
   else:
      return _trans(string, offset)
########################################################################
# projection sub-functions and objects
########################################################################
# function object for projection
class Projection:
   def __init__(self, x_index, y_index, scale_factor):
      self.x_index = x_index
      self.y_index = y_index
      self.factor = scale_factor
########################################################################
def _proj(coord, factor):
   return str(float(coord)*factor)
########################################################################
def _project(string, factor):
   if string[-1] == ",":
      return _proj(_isolate(string), factor)+","
   else:
      return _proj(string, factor)
########################################################################
# resizing sub-functions and objects
########################################################################
# function object for resizing
class Resizing:
   def __init__(self, x_index, y_index, center_coordinate, scale_factor):
      self.x_index = x_index
      self.y_index = y_index
      self.factor = scale_factor
      self.center = center_coordinate
########################################################################
def _res(coord, c_coord, factor):
   # get direction vector (point to translate) - (center)
   # multiply direction vector by scale factor
   # add scaled direction vector to center to get traslated point
   return str(c_coord+((float(coord) - c_coord)*factor))
########################################################################
def _resize(string, center, factor):
   if string[-1] == ",":
      return _res(_isolate(string), center[0], factor)+","
   else:
      return _res(string, center[1], factor)
########################################################################
# symmetrization sub-functions and objects
########################################################################
# function object for symmetrization
class uSymmetrization:
   def __init__(self, x_index, y_index, center_coordinate, rot_angle, uID):
      self.x_index = x_index
      self.y_index = y_index
      self.angle = rot_angle
      self.center = center_coordinate
      self.ID = uID

   def new_ID(self):
      self.ID[0]+=1
      return self.ID[0]
########################################################################
# function object for symmetrization
class mSymmetrization:
   def __init__(self, x_index, y_index, center_coordinate, rot_angle, mIDs):
      self.x_index = x_index
      self.y_index = y_index
      self.angle = rot_angle
      self.center = center_coordinate
      self.IDs = mIDs

   def new_ID(self, m):
      self.IDs[m]+=1
      return self.IDs[m]
########################################################################
def _duplicate(lines, begin, end, FO, marker, option):
   ret = str()
   times = 360/int(FO.angle)
   for t in range(1,times):
      ret += make_head(lines[begin], FO, marker, option)
      for l in lines[begin+1:end-2]:
         ret+=l
      if option == "marker":
         ret+=lines[end-2]
         posLine = lines[end-1].split(" ")
      elif option == "unit":
         posLine = lines[end-2].split(" ")

      new_point_coords = _symmetrize(posLine[FO.x_index],
                                                    posLine[FO.y_index], FO, t)
      posLine[FO.x_index] = str(new_point_coords[0])+","
      posLine[FO.y_index] = str(new_point_coords[1])

      ret+=(" ".join(posLine))
      if option == "unit":
         ret+=_rotate((lines[end-1]), FO, t)
      ret+=(lines[end])
   return ret
########################################################################
def _rotate(line, FO, factor):
   line = line.split(" ")
   orientation = float(_isolate(line[-3]))
   line[-3] = str(radians(-1*factor*FO.angle)+orientation)+","
   return " ".join(line)
########################################################################
def make_head(headline, FO, marker, option):

   if option == "unit":

      nID = FO.new_ID()
      if nID < 10:
         nID = "0"+str(nID)
      else:
         nID = str(nID)
      head = headline.split("_") #[ "['UNIT", "3']" ]
      head = head[0]+ "_"+str(nID)+"'] = {\r\n"
      return head

   elif option == "marker":
      nID = FO.new_ID(marker)
      if nID < 10:
         nID = "0"+str(nID)
      else:
         nID = str(nID)
      head = headline.split(" ")
      head[-3] = nID+"']"
      return " ".join(head)
########################################################################
def exists_marker(line, markers):
   for m, c in markers.iteritems():
      if (m in line) and ("{\r\n" in line):
         return m
   return False
########################################################################
def exists_unit(line):
   if ("UNIT" in line) and ("{\r\n" in line):
      return True
   return False
########################################################################
def find_end_of_block(i, lines):
   j = i
   for line in lines[i:]:
      if "},\r\n" in line and line[line.index("},\r\n")-2] == " ":
         return j
      j+=1
########################################################################
def _sym(x, y, FO, f):
   a = FO.angle
   x = x-FO.center[0]
   y = y-FO.center[1]

   sin_a = sin(f*radians(a))
   cos_a = cos(f*radians(a))

   x1 = cos_a*x + (-1*sin_a*y)
   y1 = sin_a*x + cos_a*y

   x = x1+FO.center[0]
   y = y1+FO.center[1]
   return (x, y)
########################################################################
def _symmetrize(string_x, string_y, FO, factor):
   x = float(_isolate(string_x))
   y = float(string_y)
   return _sym(x, y, FO, factor)
########################################################################
# general sub-functions
########################################################################
def _isolate(string):
   return string.split(",")[0]
########################################################################
def _alter(line, FO, mode):
   if mode == "-t":
      line[FO.x_index] = _translate(line[FO.x_index], FO.x_offset)
      line[FO.y_index] = _translate(line[FO.y_index], FO.y_offset)
   elif mode == "-p":
      line[FO.x_index] = _project(line[FO.x_index], FO.factor)
      line[FO.y_index] = _project(line[FO.y_index], FO.factor)
   elif mode == "-r":
      line[FO.x_index] = _resize(line[FO.x_index], FO.center, FO.factor)
      line[FO.y_index] = _resize(line[FO.y_index], FO.center, FO.factor)
   return line
########################################################################
def find_unit(line, max_id):
   if "UNIT" in line:
      line = line.split("_")
      # get unit ID
      u_id = int(line[1].split("'")[0])
      # update max unit id if necessary
      if u_id > max_id[0]:
         return [u_id]
   return max_id
########################################################################
def find_marker(line, max_ids):
   # get the marker name that might be n line
   markers = [m for m in max_ids if m in line]
   line = line.split(" ")
   if (len(markers) > 0) and ("{\r\n" in line):
      m = markers[0]
      # isolate the marker ID
      m_id = int(line[-3].split("'")[0])
      # compare if larger than largest ID for marker type
      if m_id > max_ids[m]:
         max_ids[m] = m_id
   return max_ids
########################################################################
# driver functions
########################################################################
# translates coordinates
def translate(option, x_offset, y_offset, iFile, oFile):

   with open(iFile, "r") as I:
      with open(oFile, "w") as O:

         # replace decal or prop coords
         if option in ["-p", "-d"]:
            for line in I:
               line = line.split(" ")
               if line[0] == "position":
                  line = _alter(line, Translation(3, 5, x_offset, y_offset),
                                argv[1])
               line = " ".join(line)
               O.write(line)

         # replace unit and marker coords
         if option == "-mu":
            for line in I:
               line = line.split(" ")
               if "['position']" in line:
                  line = _alter(line, Translation(23, 25, x_offset, y_offset),
                                argv[1])
               if "Position" in line:
                  line = _alter(line, Translation(35, 37, x_offset, y_offset),
                                argv[1])
               line = " ".join(line)
               O.write(line)
########################################################################
# projects coordinates
def project(option, factor, iFile, oFile):

   with open(iFile, "r") as I:
      with open(oFile, "w") as O:

         # replace decal or prop coords
         if option in ["-p", "-d"]:
            for line in I:
               line = line.split(" ")
               if line[0] == "position":
                  line = _alter(line, Projection(3, 5, factor), argv[1])
               line = " ".join(line)
               O.write(line)

         # replace unit and marker coords
         if option == "-mu":
            for line in I:
               line = line.split(" ")
               if "['position']" in line:
                  line = _alter(line, Projection(23, 25, factor), argv[1])
               if "Position" in line:
                  line = _alter(line, Projection(35, 37, factor), argv[1])
               line = " ".join(line)
               O.write(line)
########################################################################
# scales coordinates
def resize(option, center_x, center_y, factor, iFile, oFile):

   center = tuple((center_x, center_y))

   with open(iFile, "r") as I:
      with open(oFile, "w") as O:

         # replace decal or prop coords
         if option in ["-p", "-d"]:
            for line in I:
               line = line.split(" ")
               if line[0] == "position":
                  line = _alter(line, Resizing(3, 5, center, factor), argv[1])
               line = " ".join(line)
               O.write(line)

         # replace unit and marker coords
         if option == "-mu":
            for line in I:
               line = line.split(" ")
               if "['position']" in line:
                  line = _alter(line, Resizing(23, 25, center, factor), argv[1])
               if "Position" in line:
                  line = _alter(line, Resizing(35, 37, center, factor), argv[1])
               line = " ".join(line)
               O.write(line)
########################################################################
def symmetrize(option, center_x, center_y, angle, iFile, oFile):

   center = tuple((center_x, center_y))

   # first get the highest Marker and Unit IDs
   m_max = {"Blank":-1, "Camera":-1, "Combat":-1, "Defensive":-1,
            "Expansion":-1, "Hydrocarbon":-1, "Island":-1, "Mass":-1,
            "Naval":-1, "Experimental":-1, "Rally":-1, "Definition":-1,
            "Generator":-1}
   u_max = [0]

   with open(iFile, "r") as I:
      for line in I:
         m_max = find_marker(line, m_max)
         u_max = find_unit(line, u_max)

   with open(iFile, "r") as I:
      with open(oFile, "w") as O:

         lines = I.readlines()
         i = 0
         j = None

         for line in lines:
            O.write(line)
            marker = exists_marker(line, m_max)
            unit = exists_unit(line)

            if marker:
               j = find_end_of_block(i, lines)
               ms = mSymmetrization(23, 25, center, angle, m_max)
               new_entries = _duplicate(lines, i, j, ms, marker, "marker")
               m_max = ms.IDs

            elif unit:
               j = find_end_of_block(i, lines)
               us = uSymmetrization(35, 37, center, angle, u_max)
               new_entries = _duplicate(lines, i, j, us, marker, "unit")

            if i == j:
               j = None
               O.write(new_entries)
            i+=1
########################################################################
#
########################################################################
if __name__ == "__main__":

   if len(argv) == 1:
      print ("-h for usage")
   elif len(argv) == 2 and argv[1] == "-h":
      print (
      "\nLionhardt's coordinate transformation script\n\n"
    "-t -- translate\t(wirte t for more info)\n"
    "-p -- project\t(write p for more info)\n"
    "-r -- resize\t(write r for more info)\n"
    "-s -- rotate\t(write ro for more info)\n"
    )
      o = raw_input()
      while o not in ["t", "p", "r", "s"]:
         print "invalid option"
         o = raw_input()
      if o == "t":
         print (
         "\nTRANSLATE\n---------\n"
          "Moves objects without altering their relative distances to"
          " each other.\n"
          "usage: python transform -r [-d, -p, -mu] <x-offset> <y-offset>"
          " <input file> <output file>\n"
          "choose -d for decals, -p for props and -mu for markers and"
          " units"
          )

      elif o == "p":
         print (
         "\nPROJECT\n-------\n"
         "Projects objects onto a different map size, preserving"
         " relative distances.\n"
         "usage: python transform -p [-d, -p, -mu] <scale factor>"
         " <input file> <output file>\n"
         "choose -d for decals, -p for props and -mu for markers and"
         " units"
         )

      elif o == "r":
       print (
       "\nRESIZE\n------\n"
       "Alters distances between objects through a center projection\n"
       "usage: python transform -r [-d, -p, -mu] <center x-coordinate>"
       " <center y-coordinate> <scale factor> <input file> <output file>\n"
       "choose -d for decals, -p for props and -mu for markers and"
       " units\n"
       )

      elif o == "s":
         print (
         "\SYMMETRIZE\n----------\n"
         "Rotates objects around center point and copies them <360/angle>"
         " times, rounding down.\n"
         "usage: python transform -ro [-mu] <center x-coordinate>"
         " <center y-coordinate> <angle> <input file> <output file>\n"
         "choose -d for decals, -p for props and -mu for markers and"
         " units\n"
         )

   else:

      if argv[-2] == argv[-1]:
            sys.stderr.write(
            "Error: input file must be different from output file\n"
            )

      elif (argv[1] == "-t") and (len(argv) == 7):
         translate(argv[2], float(argv[3]), float(argv[4]), argv[5], argv[6])

      elif (argv[1] == "-p") and (len(argv) == 6):
         project(argv[2], float(argv[3]), argv[4], argv[5])

      elif (argv[1] == "-r") and (len(argv) == 8):
         resize(argv[2], float(argv[3]), float(argv[4]), float(argv[5]),
                argv[6], argv[7])

      elif (argv[1] == "-s") and (len(argv) == 8):
         symmetrize(argv[2], float(argv[3]), float(argv[4]), float(argv[5]),
                argv[6], argv[7])

# example call:
# py transform.py -t -mu -200 -200 MyMap_save.lua MyMap_save.lua.trans
Last edited by Lionhardt on 01 Aug 2015, 03:56, edited 2 times in total.
Help me make better maps for all of us, visit my Mapping Thread.

Maps needing gameplay feedback:
Spoiler: show
[list updated last: 31.1.2018]

(maps available in the vault)

- Hexagonian Drylands
- Fervent Soil and Torrid Suns

YouTube Channel
User avatar
Lionhardt
Contributor
 
Posts: 1070
Joined: 29 Jan 2013, 23:44
Has liked: 188 times
Been liked: 144 times
FAF User Name: Lionhardt

Re: Object Coordinate Transformation Script

Postby Lionhardt » 29 Jul 2015, 05:58

Added new functionality: Symmetrizing markers. Will do Units next. You enter some angle and it will symmetrize all your markers by copying them (360/angle) times. So, for example 4 times in case of 90 degrees (360/90 = 4).

I hope I didn't break any previous stuff. Was too lazy to test.



And ...omgThisCodeIsSoMessyPleaseDontLookAtItItsEmbarrassing
Attachments
sym2.jpg
sym2.jpg (101.02 KiB) Viewed 6335 times
sym.jpg
sym.jpg (103.01 KiB) Viewed 6345 times
Last edited by Lionhardt on 29 Jul 2015, 16:47, edited 4 times in total.
Help me make better maps for all of us, visit my Mapping Thread.

Maps needing gameplay feedback:
Spoiler: show
[list updated last: 31.1.2018]

(maps available in the vault)

- Hexagonian Drylands
- Fervent Soil and Torrid Suns

YouTube Channel
User avatar
Lionhardt
Contributor
 
Posts: 1070
Joined: 29 Jan 2013, 23:44
Has liked: 188 times
Been liked: 144 times
FAF User Name: Lionhardt

Re: Object Coordinate Transformation Script

Postby nine2 » 29 Jul 2015, 06:09

cool
nine2
Councillor - Promotion
 
Posts: 2416
Joined: 16 Apr 2013, 10:10
Has liked: 285 times
Been liked: 515 times
FAF User Name: Anihilnine

Re: Object Coordinate Transformation Script

Postby ZenTractor » 29 Jul 2015, 13:01

That's pretty damn cool, Lionhardt! Since you've got rotations and translations, you can combine them in series to make rotations around arbitrary points, which might be useful for making small things like outposts symmetric.

This website has some details: http://www.euclideanspace.com/maths/geo ... oundPoint/
It's in 3d, and rather maths-y, but if you can grok it it should give you a usable method.
ZenTractor
Avatar-of-War
 
Posts: 67
Joined: 10 Apr 2014, 04:09
Has liked: 10 times
Been liked: 2 times
FAF User Name: ZenTractor

Re: Object Coordinate Transformation Script

Postby Lionhardt » 29 Jul 2015, 16:09

Well, the thing is, my script does the operation on ALL objects that are in the specified category, because there is no way to identify groups of object that somehow belong together. I mean, I could probably write some kind of filter, so only objects that are at most x length units away from some specified point get affected...

As I said, I am writing this in python, because I don't know lua. And the files I manipulate are apparently lua files. Because of that I do all operations as string manipulation, which is incredibly hacky. And the more complex the stuff the worse it gets with this hacky approach. So, as of now I cannot really rotate an object and then pass it to the translation function.. because I am merely manipulating strings, as I don't parse the files with lua.
Help me make better maps for all of us, visit my Mapping Thread.

Maps needing gameplay feedback:
Spoiler: show
[list updated last: 31.1.2018]

(maps available in the vault)

- Hexagonian Drylands
- Fervent Soil and Torrid Suns

YouTube Channel
User avatar
Lionhardt
Contributor
 
Posts: 1070
Joined: 29 Jan 2013, 23:44
Has liked: 188 times
Been liked: 144 times
FAF User Name: Lionhardt

Re: Object Coordinate Transformation Script

Postby Sheeo » 29 Jul 2015, 16:26

Lionhardt wrote:Because the editor lacks any such tool I made a script that does the job for me. Its quick and dirty. But it "just works".


Nice tool lionhardt, thanks for putting time into making that :)

I'd recommend you to use http://docopt.org/ for documenting scripts like this in the future. Otherwise looks nice and useful :)
Support FAF on patreon: https://www.patreon.com/faf?ty=h

Peek at our continued development on github: https://github.com/FAForever
Sheeo
Councillor - Administrative
 
Posts: 1038
Joined: 17 Dec 2013, 18:57
Has liked: 109 times
Been liked: 233 times
FAF User Name: Sheeo

Re: Object Coordinate Transformation Script

Postby Lionhardt » 29 Jul 2015, 20:24

That looks friggin cool, thanks, will try it out!
Help me make better maps for all of us, visit my Mapping Thread.

Maps needing gameplay feedback:
Spoiler: show
[list updated last: 31.1.2018]

(maps available in the vault)

- Hexagonian Drylands
- Fervent Soil and Torrid Suns

YouTube Channel
User avatar
Lionhardt
Contributor
 
Posts: 1070
Joined: 29 Jan 2013, 23:44
Has liked: 188 times
Been liked: 144 times
FAF User Name: Lionhardt

Re: Object Coordinate Transformation Script

Postby Lionhardt » 31 Jul 2015, 23:10

I am stuck with the unit rotation, becuase I don't understadnm what format the editor uses to encode objects' orientations.

Anybody any idea what the second entry in the orientations data structure are?

Code: Select all
['UNIT_1'] = {

                                type = 'ura0303',

                                orders = '',

                                platoon = '',

                                Position = { 128.000000, 64.000000, 144.000000 },

                                Orientation = { 0.000000, 0.000000, 0.000000 },

                            },

                            ['UNIT_2'] = {

                                type = 'ura0303',

                                orders = '',

                                platoon = '',

                                Position = { 128.000000, 64.000000, 147.000000 },

                                Orientation = { 0.000000, 1.553343, 0.000000 }, -- corresponds to approx. 90 degree rotation

                            },

                            ['UNIT_3'] = {

                                type = 'ura0303',

                                orders = '',

                                platoon = '',

                                Position = { 128.000000, 64.000000, 150.000000 },

                                Orientation = { 0.000000, 3.115413, 0.000000 }, -- corresponds to approx. 180 degree rotation

                            },

                            ['UNIT_4'] = {

                                type = 'ura0303',

                                orders = '',

                                platoon = '',

                                Position = { 128.000000, 64.000000, 153.000000 },

                                Orientation = { 0.000000, 4.738566, 0.000000 }, -- corresponds to approx. 270 degree rotation

                            },
Help me make better maps for all of us, visit my Mapping Thread.

Maps needing gameplay feedback:
Spoiler: show
[list updated last: 31.1.2018]

(maps available in the vault)

- Hexagonian Drylands
- Fervent Soil and Torrid Suns

YouTube Channel
User avatar
Lionhardt
Contributor
 
Posts: 1070
Joined: 29 Jan 2013, 23:44
Has liked: 188 times
Been liked: 144 times
FAF User Name: Lionhardt

Re: Object Coordinate Transformation Script

Postby ZenTractor » 01 Aug 2015, 02:22

Looks like it's in radians. So pi radians is 180 degrees around
ZenTractor
Avatar-of-War
 
Posts: 67
Joined: 10 Apr 2014, 04:09
Has liked: 10 times
Been liked: 2 times
FAF User Name: ZenTractor

Re: Object Coordinate Transformation Script

Postby Lionhardt » 01 Aug 2015, 03:50

Thanks!

Okay, it can do this now:

sym3.jpg
sym3.jpg (359.04 KiB) Viewed 6247 times




Will now try to incorporate docopt... have not quite figured it out yet...


Once I am done with the first pass I might actually redo the whole thing in a more sophisticated manner. For that I will need to read data from the files and build objects from it, that I can modify, which have printing methods that produce the correct lua string representations again. This way multiple transformation operations could be applied to the same object.

(probably learning some lua would be less of a hassle, but oh well^^)
Help me make better maps for all of us, visit my Mapping Thread.

Maps needing gameplay feedback:
Spoiler: show
[list updated last: 31.1.2018]

(maps available in the vault)

- Hexagonian Drylands
- Fervent Soil and Torrid Suns

YouTube Channel
User avatar
Lionhardt
Contributor
 
Posts: 1070
Joined: 29 Jan 2013, 23:44
Has liked: 188 times
Been liked: 144 times
FAF User Name: Lionhardt

Next

Return to Mapping

Who is online

Users browsing this forum: No registered users and 1 guest