Home | History | Annotate | Download | only in tools
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17  /**
     18  * @fileoverview This implements a Photoshop script that can be used to generate
     19  * collision information for the AndouKun game engine.  This tool walks over
     20  * each path in the current document and generates a list of edges and normals
     21  * in a new document.  It is intended to be used on a file containing
     22  * graphical representations of the collision tiles used by the engine.  Each
     23  * path in the file must be closed and may not contain any curved points
     24  * (the tool assumes that the line between any two points in a given path is
     25  * straight).  Only one shape may be contained per path layer (each path must go
     26  * in its own path layer).  This tool can also output a graphical version of its
     27  * edge calculation for debugging purposes.
     28  */
     29 
     30 /* If set to true, the computation will be rendered graphically to the output
     31    file */
     32 var drawOutput = false;
     33 /* If true, the computation will be printed in a text layer in the
     34    output file.*/
     35 var printOutput = true;
     36 
     37 // Back up the ruler units that this file uses before switching to pixel units.
     38 var defaultRulerUnits = app.preferences.rulerUnits;
     39 app.preferences.rulerUnits = Units.PIXELS;
     40 
     41 var tileSizeX = prompt("Tile pixel width:");
     42 var tileSizeY = prompt("Tile pixel height:");
     43 
     44 var documentWidth = app.activeDocument.width;
     45 var documentHeight = app.activeDocument.height;
     46 
     47 var tilesPerRow = documentWidth / tileSizeX;
     48 var tilesPerColumn = documentHeight / tileSizeY;
     49 
     50 var tiles = new Array();
     51 tiles.length = tilesPerRow * tilesPerColumn;
     52 
     53 // Walk the list of paths and extract edges and normals.  Store these in
     54 // an array by tile.
     55 var pathList = app.activeDocument.pathItems;
     56 for (pathIndex = 0; pathIndex < pathList.length; pathIndex++) {
     57   var main_path = pathList[pathIndex];
     58   if (main_path) {
     59     var itemList = main_path.subPathItems;
     60     if (!itemList) {
     61       alert("Path has no sub items!");
     62     } else {
     63       for (var x = 0; x < itemList.length; x++) {
     64         var item = itemList[x];
     65         var points = item.pathPoints;
     66         var tile = new Object;
     67         tile.edges = new Array();
     68 
     69         var totalX = 0;
     70         var totalY = 0;
     71         for (var y = 0; y < points.length; y++) {
     72           var firstPoint = points[y];
     73           var lastPoint = points[(y + 1) % points.length];
     74 
     75           var edge = new Object;
     76 
     77           edge.startX = firstPoint.anchor[0];
     78           edge.startY = firstPoint.anchor[1];
     79 
     80           edge.endX = lastPoint.anchor[0];
     81           edge.endY = lastPoint.anchor[1];
     82 
     83           var normalX = -(edge.endY - edge.startY);
     84           var normalY = edge.endX - edge.startX;
     85 
     86           var normalLength = Math.sqrt((normalX * normalX) + (normalY * normalY));
     87           normalX /= normalLength;
     88           normalY /= normalLength;
     89 
     90           edge.normalX = normalX;
     91           edge.normalY = normalY;
     92 
     93           if (normalX == 0 && normalY == 0) {
     94             alert("Zero length normal calculated at path " + pathIndex);
     95           }
     96 
     97           var normalLength2 = Math.sqrt((normalX * normalX) + (normalY * normalY));
     98           if (normalLength2 > 1 || normalLength2 < 0.9) {
     99             alert("Normal of invalid length (" + normalLength2 + ") found at path " + pathIndex);
    100           }
    101 
    102           totalX += edge.endX;
    103           totalY += edge.endY;
    104 
    105           var width = edge.endX - edge.startX;
    106           var height = edge.endY - edge.startY;
    107 
    108           edge.centerX = edge.endX - (width / 2);
    109           edge.centerY = edge.endY - (height / 2);
    110 
    111           tile.edges.push(edge);
    112         }
    113 
    114         totalX /= points.length;
    115         totalY /= points.length;
    116         tile.centerX = totalX;
    117         tile.centerY = totalY;
    118 
    119         var column = Math.floor(tile.centerX / tileSizeX);
    120         var row = Math.floor(tile.centerY / tileSizeY);
    121 
    122         tile.xOffset = column * tileSizeX;
    123         tile.yOffset = row * tileSizeY;
    124 
    125         tile.centerX -= tile.xOffset;
    126         tile.centerY -= tile.yOffset;
    127 
    128         var tileIndex = Math.floor(row * tilesPerRow + column);
    129         tiles[tileIndex] = tile;
    130 
    131       }
    132     }
    133   }
    134 }
    135 
    136 var outputString = "";
    137 
    138 // For each tile print the edges to a string.
    139 for (var x = 0; x < tiles.length; x++) {
    140   if (tiles[x]) {
    141     var tile = tiles[x];
    142     for (var y = 0; y < tile.edges.length; y++) {
    143       var edge = tile.edges[y];
    144 
    145       // convert to tile space
    146       edge.startX -= tile.xOffset;
    147       edge.startY -= tile.yOffset;
    148       edge.endX -= tile.xOffset;
    149       edge.endY -= tile.yOffset;
    150       edge.centerX -= tile.xOffset;
    151       edge.centerY -= tile.yOffset;
    152 
    153       // The normals that we calculated previously might be facing the wrong
    154       // direction.  Detect this case and correct it by checking to see if
    155       // adding the normal to a point on the edge moves the point closer or
    156       // further from the center of the shape.
    157       if (Math.abs(edge.centerX - tile.centerX) >
    158             Math.abs((edge.centerX + edge.normalX) - tile.centerX)) {
    159         edge.normalX *= -1;
    160         edge.normalY *= -1;
    161       }
    162 
    163       if (Math.abs(edge.centerY - tile.centerY) >
    164             Math.abs((edge.centerY + edge.normalY) - tile.centerY)) {
    165         edge.normalX *= -1;
    166         edge.normalY *= -1;
    167       }
    168 
    169 
    170       // Convert to left-handed GL space (the origin is at the bottom-left).
    171       edge.normalY *= -1;
    172       edge.startY = tileSizeY - edge.startY;
    173       edge.endY = tileSizeY - edge.endY;
    174       edge.centerY = tileSizeY - edge.centerY;
    175 
    176       outputString += x + ":" + Math.floor(edge.startX) + "," +
    177           Math.floor(edge.startY) + ":" + Math.floor(edge.endX) + "," +
    178           Math.floor(edge.endY) + ":" + edge.normalX + "," + edge.normalY +
    179           "\r";
    180     }
    181   }
    182 }
    183 
    184 
    185 if (outputString.length > 0) {
    186 
    187     var newDoc = app.documents.add(600, 700, 72.0, "Edge Output",
    188         NewDocumentMode.RGB);
    189 
    190     if (drawOutput) {
    191       // Render the edges and normals to the new document.
    192       var pathLayer = newDoc.artLayers.add();
    193       newDoc.activeLayer = pathLayer;
    194 
    195       // draw the edges to make sure everything works
    196       var black = new SolidColor;
    197       black.rgb.red = 0;
    198       black.rgb.blue = 0;
    199       black.rgb.green = 0;
    200 
    201       var redColor = new SolidColor;
    202       redColor.rgb.red = 255;
    203       redColor.rgb.blue = 0;
    204       redColor.rgb.green = 0;
    205 
    206       var greenColor = new SolidColor;
    207       greenColor.rgb.red = 0;
    208       greenColor.rgb.blue = 0;
    209       greenColor.rgb.green = 255;
    210 
    211       var blueColor = new SolidColor;
    212       blueColor.rgb.red = 0;
    213       blueColor.rgb.blue = 255;
    214       blueColor.rgb.green = 0;
    215 
    216       var lineIndex = 0;
    217       for (var x = 0; x < tiles.length; x++) {
    218         if (tiles[x]) {
    219           var tile = tiles[x];
    220           var lineArray = new Array();
    221           var offsetX = Math.floor(x % tilesPerRow) * tileSizeX;
    222           var offsetY = Math.floor(x / tilesPerRow) * tileSizeY;
    223 
    224           for (var y = 0; y < tile.edges.length; y++) {
    225             var edge = tile.edges[y];
    226 
    227             lineArray[y] = Array(offsetX + edge.startX, offsetY + edge.startY);
    228           }
    229 
    230           // I tried to do this by stroking paths, but the documentation
    231           // provided by Adobe is faulty (their sample code doesn't run).  The
    232           // same thing can be accomplished with selections instead.
    233           newDoc.selection.select(lineArray);
    234           newDoc.selection.stroke(black, 2);
    235 
    236           for (var y = 0; y < tile.edges.length; y++) {
    237             var edge = tile.edges[y];
    238 
    239              var normalX = Math.round(tile.centerX +
    240                 (edge.normalX * (tileSizeX / 2)));
    241              var normalY = Math.round(tile.centerY +
    242                 (edge.normalY * (tileSizeY / 2)));
    243 
    244              var tileCenterArray = new Array();
    245              tileCenterArray[0] = new Array(offsetX + tile.centerX - 1,
    246                 offsetY + tile.centerY - 1);
    247              tileCenterArray[1] = new Array(offsetX + tile.centerX - 1,
    248                 offsetY + tile.centerY + 1);
    249              tileCenterArray[2] = new Array(offsetX + tile.centerX + 1,
    250                 offsetY + tile.centerY + 1);
    251              tileCenterArray[3] = new Array(offsetX + tile.centerX + 1,
    252                 offsetY + tile.centerY - 1);
    253              tileCenterArray[4] = new Array(offsetX + normalX - 1,
    254                 offsetY + normalY - 1);
    255              tileCenterArray[5] = new Array(offsetX + normalX + 1,
    256                 offsetY + normalY + 1);
    257              tileCenterArray[6] = new Array(offsetX + tile.centerX,
    258                 offsetY + tile.centerY);
    259 
    260              newDoc.selection.select(tileCenterArray);
    261              newDoc.selection.fill(redColor);
    262 
    263              var centerArray = new Array();
    264              centerArray[0] = new Array(offsetX + edge.centerX - 1,
    265                 offsetY + edge.centerY - 1);
    266              centerArray[1] = new Array(offsetX + edge.centerX - 1,
    267                 offsetY + edge.centerY + 1);
    268              centerArray[2] = new Array(offsetX + edge.centerX + 1,
    269                 offsetY + edge.centerY + 1);
    270              centerArray[3] = new Array(offsetX + edge.centerX + 1,
    271                 offsetY + edge.centerY - 1);
    272 
    273              newDoc.selection.select(centerArray);
    274              newDoc.selection.fill(greenColor);
    275 
    276           }
    277 
    278         }
    279       }
    280     }
    281 
    282     if (printOutput) {
    283       var textLayer = newDoc.artLayers.add();
    284       textLayer.kind = LayerKind.TEXT;
    285       textLayer.textItem.contents = outputString;
    286     }
    287 }
    288 
    289 preferences.rulerUnits = defaultRulerUnits;
    290 
    291 // Convenience function for clamping negative values to zero.  Trying to select
    292 // areas outside the canvas causes Bad Things.
    293 function clamp(input) {
    294   if (input < 0) {
    295     return 0;
    296   }
    297   return input;
    298 }
    299