/*
* 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;
}