/* -*- tab-width: 4 -*-
*
* Electric(tm) VLSI Design System
*
* File: PlacementFrame.java
*
* Copyright (c) 2009 Sun Microsystems and Static Free Software
*
* Electric(tm) is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* Electric(tm) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Electric(tm); see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, Mass 02111-1307, USA.
*/
package com.sun.electric.tool.placement;
import com.sun.electric.database.ImmutableArcInst;
import com.sun.electric.database.geometry.EPoint;
import com.sun.electric.database.geometry.ERectangle;
import com.sun.electric.database.geometry.Orientation;
import com.sun.electric.database.geometry.Poly;
import com.sun.electric.database.hierarchy.Cell;
import com.sun.electric.database.hierarchy.Export;
import com.sun.electric.database.hierarchy.Library;
import com.sun.electric.database.hierarchy.View;
import com.sun.electric.database.network.Netlist;
import com.sun.electric.database.network.Network;
import com.sun.electric.database.prototype.NodeProto;
import com.sun.electric.database.prototype.PortCharacteristic;
import com.sun.electric.database.prototype.PortProto;
import com.sun.electric.database.text.TextUtils;
import com.sun.electric.database.topology.ArcInst;
import com.sun.electric.database.topology.NodeInst;
import com.sun.electric.database.topology.PortInst;
import com.sun.electric.database.variable.TextDescriptor;
import com.sun.electric.database.variable.Variable;
import com.sun.electric.database.variable.Variable.Key;
import com.sun.electric.technology.PrimitiveNode;
import com.sun.electric.technology.technologies.Generic;
import com.sun.electric.technology.technologies.Schematics;
import com.sun.electric.tool.placement.forceDirected1.PlacementForceDirectedTeam5;
import com.sun.electric.tool.placement.forceDirected2.PlacementForceDirectedStaged;
import com.sun.electric.tool.placement.genetic1.g1.GeneticPlacement;
import com.sun.electric.tool.placement.genetic2.PlacementGenetic;
import com.sun.electric.tool.placement.simulatedAnnealing1.SimulatedAnnealing;
import com.sun.electric.tool.placement.simulatedAnnealing2.PlacementSimulatedAnnealing;
import com.sun.electric.tool.user.IconParameters;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Class to define a framework for Placement algorithms.
* To make Placement algorithms easier to write, all Placement algorithms must extend this class.
* The Placement algorithm then defines two methods:
*
* public String getAlgorithmName()
* returns the name of the Placement algorithm
*
* void runPlacement(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks, String cellName)
* runs the placement on the "nodesToPlace", calling each PlacementNode's "setPlacement()"
* and "setOrientation()" methods to establish the proper placement.
*
* To avoid the complexities of the Electric database, four shadow-classes are defined that
* describe the necessary information that Placement algorithms want:
*
* PlacementNode is an actual node (primitive or cell instance) that is to be placed.
* PlacementPort is the connection site on the PlacementNode.
* Each PlacementNode has a list of zero or more PlacementPort objects,
* and each PlacementPort points to its "parent" PlacementNode.
* PlacementNetwork determines which PlacementPort objects will connect together.
* Each PlacementNetwork has a list of two or more PlacementPort objects that it connects,
* and each PlacementPort has a PlacementNetwork in which it resides.
* PlacementExport describes exports in the cell.
* Placement algorithms do not usually need this information:
* it exists as a way to communicate the information internally.
*/
public class PlacementFrame
{
/**
* Static list of all Placement algorithms.
* When you create a new algorithm, add it to the following list.
*/
private static PlacementFrame [] placementAlgorithms = {
new SimulatedAnnealing(), // team 2
new PlacementSimulatedAnnealing(), // team 6
new GeneticPlacement(), // team 3
new PlacementGenetic(), // team 4
new PlacementForceDirectedTeam5(), // team 5
new PlacementForceDirectedStaged(), // team 7
new PlacementMinCut(),
new PlacementSimple(),
new PlacementRandom()
};
/**
* Method to return a list of all Placement algorithms.
* @return a list of all Placement algorithms.
*/
public static PlacementFrame [] getPlacementAlgorithms() { return placementAlgorithms; }
/**
* Method to do Placement (overridden by actual Placement algorithms).
* @param nodesToPlace a list of all nodes that are to be placed.
* @param allNetworks a list of all networks that connect the nodes.
* @param cellName the name of the cell being placed.
*/
protected void runPlacement(List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks, String cellName) {}
/**
* Method to return the name of the placement algorithm (overridden by actual Placement algorithms).
* @return the name of the placement algorithm.
*/
public String getAlgorithmName() { return "?"; }
/**
* Class to define a node that is being placed.
* This is a shadow class for the internal Electric object "NodeInst".
* There are minor differences between PlacementNode and NodeInst,
* for example, PlacementNode is presumed to be centered in the middle, with
* port offsets based on that center, whereas the NodeInst has a cell-center
* that may not be in the middle.
*/
public static class PlacementNode
{
private NodeProto original;
private String nodeName;
private int techBits;
private double width, height;
private List<PlacementPort> ports;
private double xPos, yPos;
private Orientation orient;
private Map<Key,Object> addedVariables;
private Object userObject;
public Object getUserObject() { return userObject; }
public void setUserObject(Object obj) { userObject = obj; }
/**
* Method to create a PlacementNode object.
* @param type the original Electric type of this PlacementNode.
* @param name the name to give the node once placed (can be null).
* @param tBits the technology-specific bits of this PlacementNode
* (typically 0 except for specialized Schematics components).
* @param wid the width of this PlacementNode.
* @param hei the height of this PlacementNode.
* @param pps a list of PlacementPort on the PlacementNode, indicating connection locations.
*/
public PlacementNode(NodeProto type, String name, int tBits, double wid, double hei, List<PlacementPort> pps)
{
original = type;
nodeName = name;
techBits = tBits;
width = wid;
height = hei;
ports = pps;
}
/**
* Method to add variables to this PlacementNode.
* Variables are extra name/value pairs, for example a transistor width and length.
* @param name the Key of the variable to add.
* @param value the value of the variable to add.
*/
public void addVariable(Key name, Object value)
{
if (addedVariables == null)
addedVariables = new HashMap<Key,Object>();
addedVariables.put(name, value);
}
/**
* Method to return a list of PlacementPorts on this PlacementNode.
* @return a list of PlacementPorts on this PlacementNode.
*/
public List<PlacementPort> getPorts() { return ports; }
/**
* Method to return the width of this PlacementNode.
* @return the width of this PlacementNode.
*/
public double getWidth() { return width; }
/**
* Method to return the height of this PlacementNode.
* @return the height of this PlacementNode.
*/
public double getHeight() { return height; }
/**
* Method to set the location of this PlacementNode.
* The Placement algorithm must call this method to set the final location of the PlacementNode.
* @param x the X-coordinate of the center of this PlacementNode.
* @param y the Y-coordinate of the center of this PlacementNode.
*/
public void setPlacement(double x, double y) { xPos = x; yPos = y; }
/**
* Method to set the orientation (rotation and mirroring) of this PlacementNode.
* The Placement algorithm may call this method to set the final orientation of the PlacementNode.
* @param o the Orientation of this PlacementNode.
*/
public void setOrientation(Orientation o)
{
orient = o;
for(PlacementPort plPort : ports)
plPort.computeRotatedOffset();
}
/**
* Method to return the X-coordinate of the placed location of this PlacementNode.
* This is the location that the Placement algorithm has established for this PlacementNode.
* @return the X-coordinate of the placed location of this PlacementNode.
*/
public double getPlacementX() { return xPos; }
/**
* Method to return the Y-coordinate of the placed location of this PlacementNode.
* This is the location that the Placement algorithm has established for this PlacementNode.
* @return the Y-coordinate of the placed location of this PlacementNode.
*/
public double getPlacementY() { return yPos; }
/**
* Method to return the Orientation of this PlacementNode.
* This is the Orientation that the Placement algorithm has established for this PlacementNode.
* @return the Orientation of this PlacementNode.
*/
public Orientation getPlacementOrientation() { return orient; }
/**
* Method to return the NodeProto of this PlacementNode.
* @return the NodeProto of this PlacementNode.
*/
public NodeProto getType() { return original; }
/**
* Method to return the technology-specific information of this PlacementNode.
* @return the technology-specific information of this PlacementNode
* (typically 0 except for specialized Schematics components).
*/
public int getTechBits() { return techBits; }
public String toString()
{
String name = original.describe(false);
if (nodeName != null) name += "[" + nodeName + "]";
if (techBits != 0) name += "("+techBits+")";
return name;
}
}
/**
* Class to define ports on PlacementNode objects.
* This is a shadow class for the internal Electric object "PortInst".
*/
public static class PlacementPort
{
private double offX, offY;
private double rotatedOffX, rotatedOffY;
private PlacementNode plNode;
private PlacementNetwork plNet;
private PortProto proto;
/**
* Constructor to create a PlacementPort.
* @param x the X offset of this PlacementPort from the center of its PlacementNode.
* @param y the Y offset of this PlacementPort from the center of its PlacementNode.
* @param pp the Electric PortProto of this PlacementPort.
*/
public PlacementPort(double x, double y, PortProto pp)
{
offX = x; offY = y;
proto = pp;
}
/**
* Method to set the "parent" PlacementNode on which this PlacementPort resides.
* @param pn the PlacementNode on which this PlacementPort resides.
*/
public void setPlacementNode(PlacementNode pn) { plNode = pn; }
/**
* Method to return the PlacementNode on which this PlacementPort resides.
* @return the PlacementNode on which this PlacementPort resides.
*/
public PlacementNode getPlacementNode() { return plNode; }
/**
* Method to return the PlacementNetwork on which this PlacementPort resides.
* @param pn the PlacementNetwork on which this PlacementPort resides.
*/
public void setPlacementNetwork(PlacementNetwork pn) { plNet = pn; }
/**
* Method to return the PlacementNetwork on which this PlacementPort resides.
* @return the PlacementNetwork on which this PlacementPort resides.
* If this PlacementPort does not connect to any other PlacementPort,
* the PlacementNetwork may be null.
*/
public PlacementNetwork getPlacementNetwork() { return plNet; }
/**
* Method to return the Electric PortProto that this PlacementPort uses.
* @return the Electric PortProto that this PlacementPort uses.
*/
PortProto getPortProto() { return proto; }
/**
* Method to return the offset of this PlacementPort's X coordinate from the center of its PlacementNode.
* The offset is valid when no Orientation has been applied.
* @return the offset of this PlacementPort's X coordinate from the center of its PlacementNode.
*/
public double getOffX() { return offX; }
/**
* Method to return the offset of this PlacementPort's Y coordinate from the center of its PlacementNode.
* The offset is valid when no Orientation has been applied.
* @return the offset of this PlacementPort's Y coordinate from the center of its PlacementNode.
*/
public double getOffY() { return offY; }
/**
* Method to return the offset of this PlacementPort's X coordinate from the center of its PlacementNode.
* The coordinate assumes that the PlacementNode has been rotated by its Orientation.
* @return the offset of this PlacementPort's X coordinate from the center of its PlacementNode.
*/
public double getRotatedOffX() { return rotatedOffX; }
/**
* Method to return the offset of this PlacementPort's Y coordinate from the center of its PlacementNode.
* The coordinate assumes that the PlacementNode has been rotated by its Orientation.
* @return the offset of this PlacementPort's Y coordinate from the center of its PlacementNode.
*/
public double getRotatedOffY() { return rotatedOffY; }
/**
* Internal method to compute the rotated offset of this PlacementPort
* assuming that the Orientation of its PlacementNode has changed.
* TODO: why is this public? it should not be accessed!
*/
public void computeRotatedOffset()
{
Orientation orient = plNode.getPlacementOrientation();
if (orient == Orientation.IDENT)
{
rotatedOffX = offX;
rotatedOffY = offY;
return;
}
AffineTransform trans = orient.pureRotate();
Point2D offset = new Point2D.Double(offX, offY);
trans.transform(offset, offset);
rotatedOffX = offset.getX();
rotatedOffY = offset.getY();
}
public String toString() { return proto.getName(); }
}
/**
* Class to define networks of PlacementPort objects.
* This is a shadow class for the internal Electric object "Network", but it is simplified for Placement.
*/
public static class PlacementNetwork
{
private List<PlacementPort> portsOnNet;
/**
* Constructor to create this PlacementNetwork with a list of PlacementPort objects that it connects.
* @param ports a list of PlacementPort objects that it connects.
*/
public PlacementNetwork(List<PlacementPort> ports)
{
portsOnNet = ports;
}
/**
* Method to return the list of PlacementPort objects on this PlacementNetwork.
* @return a list of PlacementPort objects on this PlacementNetwork.
*/
public List<PlacementPort> getPortsOnNet() { return portsOnNet; }
}
/**
* Class to define an Export that will be placed in the circuit.
*/
public static class PlacementExport
{
private PlacementPort portToExport;
private String exportName;
private PortCharacteristic characteristic;
/**
* Constructor to create a PlacementExport with the information about an Export to be created.
* @param port the PlacementPort that is being exported.
* @param name the name to give the Export.
* @param chr the PortCharacteristic (input, output, etc.) to give the Export.
*/
public PlacementExport(PlacementPort port, String name, PortCharacteristic chr)
{
portToExport = port;
exportName = name;
characteristic = chr;
}
PlacementPort getPort() { return portToExport; }
String getName() { return exportName; }
PortCharacteristic getCharacteristic() { return characteristic; }
}
/**
* Entry point to do Placement of a Cell and create a new, placed Cell.
* Gathers the requirements for Placement into a collection of shadow objects
* (PlacementNode, PlacementPort, PlacementNetwork, and PlacementExport).
* Then invokes the alternate version of "doPlacement()" that works from shadow objedts.
* @param cell the Cell to place.
* Objects in that Cell will be reorganized in and placed in a new Cell.
* @return the new Cell with the placement results.
*/
public Cell doPlacement(Cell cell, Placement.PlacementPreferences prefs)
{
// get network information for the Cell
Netlist netList = cell.getNetlist();
if (netList == null)
{
System.out.println("Sorry, a deadlock aborted routing (network information unavailable). Please try again");
return null;
}
// convert nodes in the Cell into PlacementNode objects
NodeProto iconToPlace = null;
List<PlacementNode> nodesToPlace = new ArrayList<PlacementNode>();
Map<NodeInst,Map<PortProto,PlacementPort>> convertedNodes = new HashMap<NodeInst,Map<PortProto,PlacementPort>>();
List<PlacementExport> exportsToPlace = new ArrayList<PlacementExport>();
for(Iterator<NodeInst> it = cell.getNodes(); it.hasNext(); )
{
NodeInst ni = it.next();
if (ni.isIconOfParent())
{
iconToPlace = ni.getProto();
continue;
}
boolean validNode = ni.isCellInstance();
if (!validNode)
{
if (ni.getProto().getTechnology() != Generic.tech())
{
PrimitiveNode.Function fun = ni.getFunction();
if (fun != PrimitiveNode.Function.CONNECT && fun != PrimitiveNode.Function.CONTACT &&
!fun.isPin())
validNode = true;
}
if (ni.hasExports()) validNode = true;
}
if (validNode)
{
// make a list of PlacementPorts on this NodeInst
NodeProto np = ni.getProto();
List<PlacementPort> pl = new ArrayList<PlacementPort>();
Map<PortProto,PlacementPort> placedPorts = new HashMap<PortProto,PlacementPort>();
if (ni.isCellInstance())
{
for(Iterator<Export> eIt = ((Cell)np).getExports(); eIt.hasNext(); )
{
Export e = eIt.next();
Poly poly = e.getPoly();
PlacementPort plPort = new PlacementPort(poly.getCenterX(), poly.getCenterY(), e);
pl.add(plPort);
placedPorts.put(e, plPort);
}
} else
{
NodeInst niDummy = NodeInst.makeDummyInstance(np);
for(Iterator<PortInst> pIt = niDummy.getPortInsts(); pIt.hasNext(); )
{
PortInst pi = pIt.next();
Poly poly = pi.getPoly();
double offX = poly.getCenterX() - niDummy.getTrueCenterX();
double offY = poly.getCenterY() - niDummy.getTrueCenterY();
PlacementPort plPort = new PlacementPort(offX, offY, pi.getPortProto());
pl.add(plPort);
placedPorts.put(pi.getPortProto(), plPort);
}
}
// add to the list of PlacementExports
for(Iterator<Export> eIt = ni.getExports(); eIt.hasNext(); )
{
Export e = eIt.next();
PlacementPort plPort = placedPorts.get(e.getOriginalPort().getPortProto());
PlacementExport plExport = new PlacementExport(plPort, e.getName(), e.getCharacteristic());
exportsToPlace.add(plExport);
}
// make the PlacementNode for this NodeInst
String name = ni.getName();
if (ni.getNameKey().isTempname()) name = null;
PlacementNode plNode = new PlacementNode(np, name, ni.getTechSpecific(), np.getDefWidth(),
np.getDefHeight(), pl);
nodesToPlace.add(plNode);
for(PlacementPort plPort : pl)
plPort.setPlacementNode(plNode);
plNode.setOrientation(Orientation.IDENT);
convertedNodes.put(ni, placedPorts);
}
}
// gather connectivity information in a list of PlacementNetwork objects
Map<Network,PortInst[]> portInstsByNetwork = null;
if (cell.getView() != View.SCHEMATIC) portInstsByNetwork = netList.getPortInstsByNetwork();
List<PlacementNetwork> allNetworks = new ArrayList<PlacementNetwork>();
for(Iterator<Network> it = netList.getNetworks(); it.hasNext(); )
{
Network net = it.next();
List<PlacementPort> portsOnNet = new ArrayList<PlacementPort>();
PortInst[] portInsts = null;
if (portInstsByNetwork != null) portInsts = portInstsByNetwork.get(net); else
{
List<PortInst> portList = new ArrayList<PortInst>();
for(Iterator<PortInst> pIt = net.getPorts(); pIt.hasNext(); ) portList.add(pIt.next());
portInsts = portList.toArray(new PortInst[]{});
}
for(int i=0; i<portInsts.length; i++)
{
PortInst pi = portInsts[i];
NodeInst ni = pi.getNodeInst();
PortProto pp = pi.getPortProto();
Map<PortProto,PlacementPort> convertedPorts = convertedNodes.get(ni);
if (convertedPorts == null) continue;
PlacementPort plPort = convertedPorts.get(pp);
if (plPort != null) portsOnNet.add(plPort);
}
if (portsOnNet.size() > 1)
{
PlacementNetwork plNet = new PlacementNetwork(portsOnNet);
for(PlacementPort plPort : portsOnNet)
plPort.setPlacementNetwork(plNet);
allNetworks.add(plNet);
}
}
// do the placement from the shadow objects
Cell newCell = doPlacement(cell.getLibrary(), cell.noLibDescribe(), nodesToPlace, allNetworks, exportsToPlace,
iconToPlace, prefs.iconParameters);
return newCell;
}
/**
* Entry point for other tools that wish to describe a network to be placed.
* Creates a cell with the placed network.
* @param lib the Library in which to create the placed Cell.
* @param cellName the name of the Cell to create.
* @param nodesToPlace a List of PlacementNodes to place in the Cell.
* @param allNetworks a List of PlacementNetworks to connect in the Cell.
* @param exportsToPlace a List of PlacementExports to create in the Cell.
* @param iconToPlace non-null to place an instance of itself (the icon) in the Cell.
* @return the newly created Cell.
*/
public Cell doPlacement(Library lib, String cellName, List<PlacementNode> nodesToPlace, List<PlacementNetwork> allNetworks,
List<PlacementExport> exportsToPlace, NodeProto iconToPlace, IconParameters iconParameters)
{
long startTime = System.currentTimeMillis();
System.out.println("Running placement on cell '" + cellName + "' using the '" + getAlgorithmName() + "' algorithm");
// do the real work of placement
runPlacement(nodesToPlace, allNetworks, cellName);
// create a new cell for the placement results
Cell newCell = Cell.makeInstance(lib, cellName); // newCellName
// place the nodes in the new cell
Map<PlacementNode,NodeInst> placedNodes = new HashMap<PlacementNode,NodeInst>();
for(PlacementNode plNode : nodesToPlace)
{
double xPos = plNode.getPlacementX();
double yPos = plNode.getPlacementY();
Orientation orient = plNode.getPlacementOrientation();
NodeProto np = plNode.original;
if (np instanceof Cell)
{
Cell placementCell = (Cell)np;
Rectangle2D bounds = placementCell.getBounds();
Point2D centerOffset = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
orient.pureRotate().transform(centerOffset, centerOffset);
xPos -= centerOffset.getX();
yPos -= centerOffset.getY();
}
NodeInst ni = NodeInst.makeInstance(np, new Point2D.Double(xPos, yPos), np.getDefWidth(), np.getDefHeight(), newCell,
orient, plNode.nodeName, plNode.techBits);
if (ni == null) System.out.println("Placement failed to create node"); else
placedNodes.put(plNode, ni);
if (plNode.addedVariables != null)
{
for(Key key : plNode.addedVariables.keySet())
{
Object value = plNode.addedVariables.get(key);
Variable var = ni.newDisplayVar(key, value);
if (key == Schematics.SCHEM_RESISTANCE)
{
ni.setTextDescriptor(key, var.getTextDescriptor().withOff(0, 0.5).
withDispPart(TextDescriptor.DispPos.VALUE));
} else if (key == Schematics.ATTR_WIDTH)
{
ni.setTextDescriptor(key, var.getTextDescriptor().withOff(0.5, -1).
withRelSize(1).withDispPart(TextDescriptor.DispPos.VALUE));
} else if (key == Schematics.ATTR_LENGTH)
{
ni.setTextDescriptor(key, var.getTextDescriptor().withOff(-0.5, -1).
withRelSize(0.5).withDispPart(TextDescriptor.DispPos.VALUE));
} else
{
ni.setTextDescriptor(key, var.getTextDescriptor().withDispPart(TextDescriptor.DispPos.VALUE));
}
}
}
}
// place an icon if requested
if (iconToPlace != null)
{
ERectangle bounds = newCell.getBounds();
EPoint center = new EPoint(bounds.getMaxX() + iconToPlace.getDefWidth(), bounds.getMaxY() + iconToPlace.getDefHeight());
NodeInst.makeInstance(iconToPlace, center, iconToPlace.getDefWidth(), iconToPlace.getDefHeight(), newCell);
}
// place exports in the new cell
for(PlacementExport plExport : exportsToPlace)
{
PlacementPort plPort = plExport.getPort();
String exportName = plExport.getName();
PlacementNode plNode = plPort.getPlacementNode();
NodeInst newNI = placedNodes.get(plNode);
if (newNI == null) continue;
PortInst portToExport = newNI.findPortInstFromProto(plPort.getPortProto());
Export.newInstance(newCell, portToExport, exportName, plExport.getCharacteristic(), iconParameters);
}
ImmutableArcInst a = Generic.tech().unrouted_arc.getDefaultInst(newCell.getEditingPreferences());
long gridExtend = a.getGridExtendOverMin();
for(PlacementNetwork plNet : allNetworks)
{
PlacementPort lastPp = null; PortInst lastPi = null; EPoint lastPt = null;
for(PlacementPort plPort : plNet.getPortsOnNet())
{
PlacementNode plNode = plPort.getPlacementNode();
NodeInst newNi = placedNodes.get(plNode);
if (newNi != null)
{
PlacementPort thisPp = plPort;
PortInst thisPi = newNi.findPortInstFromProto(thisPp.getPortProto());
EPoint thisPt = new EPoint(plNode.getPlacementX() + plPort.getRotatedOffX(),
plNode.getPlacementY() + plPort.getRotatedOffY());
if (lastPp != null)
{
// connect them
ArcInst.newInstance(newCell, Generic.tech().unrouted_arc, null, null,
lastPi, thisPi, lastPt, thisPt, gridExtend, 0, a.flags);
}
lastPp = thisPp;
lastPi = thisPi;
lastPt = thisPt;
}
}
}
long endTime = System.currentTimeMillis();
System.out.println("\t(took " + TextUtils.getElapsedTime(endTime - startTime) + ")");
return newCell;
}
}