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 */
32var drawOutput = false;
33/* If true, the computation will be printed in a text layer in the
34   output file.*/
35var printOutput = true;
36
37// Back up the ruler units that this file uses before switching to pixel units.
38var defaultRulerUnits = app.preferences.rulerUnits;
39app.preferences.rulerUnits = Units.PIXELS;
40
41var tileSizeX = prompt("Tile pixel width:");
42var tileSizeY = prompt("Tile pixel height:");
43
44var documentWidth = app.activeDocument.width;
45var documentHeight = app.activeDocument.height;
46
47var tilesPerRow = documentWidth / tileSizeX;
48var tilesPerColumn = documentHeight / tileSizeY;
49
50var tiles = new Array();
51tiles.length = tilesPerRow * tilesPerColumn;
52
53// Walk the list of paths and extract edges and normals.  Store these in
54// an array by tile.
55var pathList = app.activeDocument.pathItems;
56for (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
136var outputString = "";
137
138// For each tile print the edges to a string.
139for (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
185if (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
289preferences.rulerUnits = defaultRulerUnits;
290
291// Convenience function for clamping negative values to zero.  Trying to select
292// areas outside the canvas causes Bad Things.
293function clamp(input) {
294  if (input < 0) {
295    return 0;
296  }
297  return input;
298}
299