| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @fileoverview This implements a Photoshop script that can be used to generate |
| * collision information for the AndouKun game engine. This tool walks over |
| * each path in the current document and generates a list of edges and normals |
| * in a new document. It is intended to be used on a file containing |
| * graphical representations of the collision tiles used by the engine. Each |
| * path in the file must be closed and may not contain any curved points |
| * (the tool assumes that the line between any two points in a given path is |
| * straight). Only one shape may be contained per path layer (each path must go |
| * in its own path layer). This tool can also output a graphical version of its |
| * edge calculation for debugging purposes. |
| */ |
| |
| /* If set to true, the computation will be rendered graphically to the output |
| file */ |
| var drawOutput = false; |
| /* If true, the computation will be printed in a text layer in the |
| output file.*/ |
| var printOutput = true; |
| |
| // Back up the ruler units that this file uses before switching to pixel units. |
| var defaultRulerUnits = app.preferences.rulerUnits; |
| app.preferences.rulerUnits = Units.PIXELS; |
| |
| var tileSizeX = prompt("Tile pixel width:"); |
| var tileSizeY = prompt("Tile pixel height:"); |
| |
| var documentWidth = app.activeDocument.width; |
| var documentHeight = app.activeDocument.height; |
| |
| var tilesPerRow = documentWidth / tileSizeX; |
| var tilesPerColumn = documentHeight / tileSizeY; |
| |
| var tiles = new Array(); |
| tiles.length = tilesPerRow * tilesPerColumn; |
| |
| // Walk the list of paths and extract edges and normals. Store these in |
| // an array by tile. |
| var pathList = app.activeDocument.pathItems; |
| for (pathIndex = 0; pathIndex < pathList.length; pathIndex++) { |
| var main_path = pathList[pathIndex]; |
| if (main_path) { |
| var itemList = main_path.subPathItems; |
| if (!itemList) { |
| alert("Path has no sub items!"); |
| } else { |
| for (var x = 0; x < itemList.length; x++) { |
| var item = itemList[x]; |
| var points = item.pathPoints; |
| var tile = new Object; |
| tile.edges = new Array(); |
| |
| var totalX = 0; |
| var totalY = 0; |
| for (var y = 0; y < points.length; y++) { |
| var firstPoint = points[y]; |
| var lastPoint = points[(y + 1) % points.length]; |
| |
| var edge = new Object; |
| |
| edge.startX = firstPoint.anchor[0]; |
| edge.startY = firstPoint.anchor[1]; |
| |
| edge.endX = lastPoint.anchor[0]; |
| edge.endY = lastPoint.anchor[1]; |
| |
| var normalX = -(edge.endY - edge.startY); |
| var normalY = edge.endX - edge.startX; |
| |
| var normalLength = Math.sqrt((normalX * normalX) + (normalY * normalY)); |
| normalX /= normalLength; |
| normalY /= normalLength; |
| |
| edge.normalX = normalX; |
| edge.normalY = normalY; |
| |
| if (normalX == 0 && normalY == 0) { |
| alert("Zero length normal calculated at path " + pathIndex); |
| } |
| |
| var normalLength2 = Math.sqrt((normalX * normalX) + (normalY * normalY)); |
| if (normalLength2 > 1 || normalLength2 < 0.9) { |
| alert("Normal of invalid length (" + normalLength2 + ") found at path " + pathIndex); |
| } |
| |
| totalX += edge.endX; |
| totalY += edge.endY; |
| |
| var width = edge.endX - edge.startX; |
| var height = edge.endY - edge.startY; |
| |
| edge.centerX = edge.endX - (width / 2); |
| edge.centerY = edge.endY - (height / 2); |
| |
| tile.edges.push(edge); |
| } |
| |
| totalX /= points.length; |
| totalY /= points.length; |
| tile.centerX = totalX; |
| tile.centerY = totalY; |
| |
| var column = Math.floor(tile.centerX / tileSizeX); |
| var row = Math.floor(tile.centerY / tileSizeY); |
| |
| tile.xOffset = column * tileSizeX; |
| tile.yOffset = row * tileSizeY; |
| |
| tile.centerX -= tile.xOffset; |
| tile.centerY -= tile.yOffset; |
| |
| var tileIndex = Math.floor(row * tilesPerRow + column); |
| tiles[tileIndex] = tile; |
| |
| } |
| } |
| } |
| } |
| |
| var outputString = ""; |
| |
| // For each tile print the edges to a string. |
| for (var x = 0; x < tiles.length; x++) { |
| if (tiles[x]) { |
| var tile = tiles[x]; |
| for (var y = 0; y < tile.edges.length; y++) { |
| var edge = tile.edges[y]; |
| |
| // convert to tile space |
| edge.startX -= tile.xOffset; |
| edge.startY -= tile.yOffset; |
| edge.endX -= tile.xOffset; |
| edge.endY -= tile.yOffset; |
| edge.centerX -= tile.xOffset; |
| edge.centerY -= tile.yOffset; |
| |
| // The normals that we calculated previously might be facing the wrong |
| // direction. Detect this case and correct it by checking to see if |
| // adding the normal to a point on the edge moves the point closer or |
| // further from the center of the shape. |
| if (Math.abs(edge.centerX - tile.centerX) > |
| Math.abs((edge.centerX + edge.normalX) - tile.centerX)) { |
| edge.normalX *= -1; |
| edge.normalY *= -1; |
| } |
| |
| if (Math.abs(edge.centerY - tile.centerY) > |
| Math.abs((edge.centerY + edge.normalY) - tile.centerY)) { |
| edge.normalX *= -1; |
| edge.normalY *= -1; |
| } |
| |
| |
| // Convert to left-handed GL space (the origin is at the bottom-left). |
| edge.normalY *= -1; |
| edge.startY = tileSizeY - edge.startY; |
| edge.endY = tileSizeY - edge.endY; |
| edge.centerY = tileSizeY - edge.centerY; |
| |
| outputString += x + ":" + Math.floor(edge.startX) + "," + |
| Math.floor(edge.startY) + ":" + Math.floor(edge.endX) + "," + |
| Math.floor(edge.endY) + ":" + edge.normalX + "," + edge.normalY + |
| "\r"; |
| } |
| } |
| } |
| |
| |
| if (outputString.length > 0) { |
| |
| var newDoc = app.documents.add(600, 700, 72.0, "Edge Output", |
| NewDocumentMode.RGB); |
| |
| if (drawOutput) { |
| // Render the edges and normals to the new document. |
| var pathLayer = newDoc.artLayers.add(); |
| newDoc.activeLayer = pathLayer; |
| |
| // draw the edges to make sure everything works |
| var black = new SolidColor; |
| black.rgb.red = 0; |
| black.rgb.blue = 0; |
| black.rgb.green = 0; |
| |
| var redColor = new SolidColor; |
| redColor.rgb.red = 255; |
| redColor.rgb.blue = 0; |
| redColor.rgb.green = 0; |
| |
| var greenColor = new SolidColor; |
| greenColor.rgb.red = 0; |
| greenColor.rgb.blue = 0; |
| greenColor.rgb.green = 255; |
| |
| var blueColor = new SolidColor; |
| blueColor.rgb.red = 0; |
| blueColor.rgb.blue = 255; |
| blueColor.rgb.green = 0; |
| |
| var lineIndex = 0; |
| for (var x = 0; x < tiles.length; x++) { |
| if (tiles[x]) { |
| var tile = tiles[x]; |
| var lineArray = new Array(); |
| var offsetX = Math.floor(x % tilesPerRow) * tileSizeX; |
| var offsetY = Math.floor(x / tilesPerRow) * tileSizeY; |
| |
| for (var y = 0; y < tile.edges.length; y++) { |
| var edge = tile.edges[y]; |
| |
| lineArray[y] = Array(offsetX + edge.startX, offsetY + edge.startY); |
| } |
| |
| // I tried to do this by stroking paths, but the documentation |
| // provided by Adobe is faulty (their sample code doesn't run). The |
| // same thing can be accomplished with selections instead. |
| newDoc.selection.select(lineArray); |
| newDoc.selection.stroke(black, 2); |
| |
| for (var y = 0; y < tile.edges.length; y++) { |
| var edge = tile.edges[y]; |
| |
| var normalX = Math.round(tile.centerX + |
| (edge.normalX * (tileSizeX / 2))); |
| var normalY = Math.round(tile.centerY + |
| (edge.normalY * (tileSizeY / 2))); |
| |
| var tileCenterArray = new Array(); |
| tileCenterArray[0] = new Array(offsetX + tile.centerX - 1, |
| offsetY + tile.centerY - 1); |
| tileCenterArray[1] = new Array(offsetX + tile.centerX - 1, |
| offsetY + tile.centerY + 1); |
| tileCenterArray[2] = new Array(offsetX + tile.centerX + 1, |
| offsetY + tile.centerY + 1); |
| tileCenterArray[3] = new Array(offsetX + tile.centerX + 1, |
| offsetY + tile.centerY - 1); |
| tileCenterArray[4] = new Array(offsetX + normalX - 1, |
| offsetY + normalY - 1); |
| tileCenterArray[5] = new Array(offsetX + normalX + 1, |
| offsetY + normalY + 1); |
| tileCenterArray[6] = new Array(offsetX + tile.centerX, |
| offsetY + tile.centerY); |
| |
| newDoc.selection.select(tileCenterArray); |
| newDoc.selection.fill(redColor); |
| |
| var centerArray = new Array(); |
| centerArray[0] = new Array(offsetX + edge.centerX - 1, |
| offsetY + edge.centerY - 1); |
| centerArray[1] = new Array(offsetX + edge.centerX - 1, |
| offsetY + edge.centerY + 1); |
| centerArray[2] = new Array(offsetX + edge.centerX + 1, |
| offsetY + edge.centerY + 1); |
| centerArray[3] = new Array(offsetX + edge.centerX + 1, |
| offsetY + edge.centerY - 1); |
| |
| newDoc.selection.select(centerArray); |
| newDoc.selection.fill(greenColor); |
| |
| } |
| |
| } |
| } |
| } |
| |
| if (printOutput) { |
| var textLayer = newDoc.artLayers.add(); |
| textLayer.kind = LayerKind.TEXT; |
| textLayer.textItem.contents = outputString; |
| } |
| } |
| |
| preferences.rulerUnits = defaultRulerUnits; |
| |
| // Convenience function for clamping negative values to zero. Trying to select |
| // areas outside the canvas causes Bad Things. |
| function clamp(input) { |
| if (input < 0) { |
| return 0; |
| } |
| return input; |
| } |