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