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