package iamrescue.agent.firebrigade;
import iamrescue.agent.firebrigade.util.RTree;
import iamrescue.belief.IAMWorldModel;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import javolution.util.FastList;
import javolution.util.FastMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import rescuecore2.misc.geometry.Line2D;
import rescuecore2.misc.geometry.Point2D;
import rescuecore2.standard.entities.Building;
import rescuecore2.standard.entities.Edge;
import rescuecore2.standard.entities.StandardEntity;
import rescuecore2.standard.entities.StandardEntityURN;
import rescuecore2.worldmodel.EntityID;
import com.infomatiq.jsi.IntProcedure;
import com.infomatiq.jsi.Rectangle;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
public class HeatTransferGraph implements Cloneable {
private Log log = LogFactory.getLog(HeatTransferGraph.class);
// Agents should have the same seed in order to make sure their beliefs are
// as similar as possible
private static final long SEED = 36728;
private Random random = new Random(SEED);
private static final double DEFAULT_RAYS_PER_DISTANCE_UNIT = 0.01;
private double maxSampleDistance;
private DirectedSparseGraph<Building, HeatTransferRelation> heatTransferGraph = new DirectedSparseGraph<Building, HeatTransferRelation>();
private RTree rtree = new RTree();
private IAMWorldModel worldModel;
private double raysPerDistanceUnit;
private boolean saveRays;
private Collection<Line2D> rays = new FastList<Line2D>();
public HeatTransferGraph(IAMWorldModel worldModel) {
this(worldModel, false);
}
public HeatTransferGraph(IAMWorldModel iwm, boolean saveRays) {
this(iwm, saveRays, DEFAULT_RAYS_PER_DISTANCE_UNIT);
}
/**
*
* @param worldModel
* @param saveRays
* if true, all emitted rays are saved (for debugging purposes
* only)
*/
public HeatTransferGraph(IAMWorldModel worldModel, boolean saveRays,
double raysPerDistanceUnit) {
this.worldModel = worldModel;
this.saveRays = saveRays;
this.raysPerDistanceUnit = raysPerDistanceUnit;
Properties props = new Properties();
props.setProperty("MaxNodeEntries", "10");
props.setProperty("MinNodeEntries", "5");
rtree.init(props);
maxSampleDistance = Math.max(worldModel.getBounds().getWidth(),
worldModel.getBounds().getHeight()) * 1.1;
Collection<StandardEntity> buildings = worldModel
.getEntitiesOfType(StandardEntityURN.BUILDING);
buildings.addAll(worldModel
.getEntitiesOfType(StandardEntityURN.AMBULANCE_CENTRE));
buildings.addAll(worldModel
.getEntitiesOfType(StandardEntityURN.FIRE_STATION));
buildings.addAll(worldModel
.getEntitiesOfType(StandardEntityURN.POLICE_OFFICE));
buildings
.addAll(worldModel.getEntitiesOfType(StandardEntityURN.REFUGE));
for (StandardEntity standardEntity : buildings) {
Building building = (Building) standardEntity;
Rectangle boundingBox = getBoundingBox(building);
rtree.add(boundingBox, building.getID().getValue());
heatTransferGraph.addVertex(building);
}
long start = System.currentTimeMillis();
log.debug("added buildings");
for (StandardEntity standardEntity : buildings) {
Building building = (Building) standardEntity;
addEdges(building);
}
long finish = System.currentTimeMillis();
System.out.println("Building Heat Transfer Graph Took "
+ (finish - start));
}
private void addEdges(Building building) {
log.debug("adding edges for " + building.getID());
int totalRays = 0;
// fire rays from each wall
for (Edge edge : building.getEdges()) {
int rays = (int) (getLength(edge) * raysPerDistanceUnit);
totalRays += rays;
// shoot a number of rays from this wall and check what's hit
for (int j = 0; j < rays; j++) {
Line2D ray = createRay(edge);
// which building did we hit?
Building neighbour;
if (lineIntersectsBuilding(ray, building)) {
// ray hit the building itself, this happens most of the
// time, so we check this to achieve speedup
neighbour = building;
} else {
neighbour = getFirstIntersectedBuilding(ray, building);
}
if (neighbour != null) {
// create or update the edge in the graph
HeatTransferRelation relation = heatTransferGraph.findEdge(
building, neighbour);
if (relation == null) {
relation = new HeatTransferRelation(building, neighbour);
heatTransferGraph
.addEdge(relation, building, neighbour);
}
relation.incrementRaysHit();
}
// else: nothing was hit, continue
}
}
Collection<HeatTransferRelation> outEdges = heatTransferGraph
.getOutEdges(building);
for (HeatTransferRelation heatTransferRelation : outEdges) {
heatTransferRelation.normalise(totalRays);
}
}
private Line2D createRay(Edge wall) {
// pick random point on wall
double rand = random.nextDouble();
double startX = wall.getStartX() + rand
* (wall.getEndX() - wall.getStartX());
double startY = wall.getStartY() + rand
* (wall.getEndY() - wall.getStartY());
// shoot ray at random angle
double angle = random.nextDouble() * 2 * Math.PI;
double endX = startX + (Math.cos(angle) * maxSampleDistance);
double endY = startY + (Math.sin(angle) * maxSampleDistance);
// debugging only: shoots rays perpendicular to wall
// Vector2D normal = edge.getLine().getDirection().getNormal();
// endX = startX + (normal.getX() * maxSampleDistance);
// endY = startY + (normal.getY() * maxSampleDistance);
return new Line2D(new Point2D(startX, startY), new Point2D(endX, endY));
}
private boolean lineIntersectsBuilding(Line2D ray, Building building) {
Double buildingIntersect = getBuildingIntersect(ray, building);
if (saveRays && buildingIntersect != null) {
rays.add(new Line2D(ray.getOrigin(), ray
.getPoint(buildingIntersect)));
}
return buildingIntersect != null;
}
/**
*
* @param ray
* @param building
* the building emitting the ray. This building is not checked
* against
* @return
*/
private Building getFirstIntersectedBuilding(Line2D ray, Building building) {
// check what is hit
ClosestBuildingIntProcedure closestBuildingIntProcedure = new ClosestBuildingIntProcedure(
ray, building);
boolean useNewMethod = true;
if (useNewMethod) {
double startX = ray.getOrigin().getX();
double startY = ray.getOrigin().getY();
double dX = ray.getDirection().getX()
/ ray.getDirection().getLength();
double dY = ray.getDirection().getY()
/ ray.getDirection().getLength();
double maxDistance = Math.min(100000, maxSampleDistance / 2);
double distanceStep = 10000;
double totalDistance = 0;
boolean done = false;
while (!done) {
double newEndPointX = startX + distanceStep * dX;
double newEndPointY = startY + distanceStep * dY;
rtree.intersects((float) startX, (float) startY,
(float) newEndPointX, (float) newEndPointY,
closestBuildingIntProcedure);
if (closestBuildingIntProcedure.getClosestBuildingIntersect() != null) {
done = true;
} else {
startX = newEndPointX;
startY = newEndPointY;
if (startX > worldModel.getBounds().getMaxX()) {
done = true;
} else if (startX < worldModel.getBounds().getMinX()) {
done = true;
} else if (startY > worldModel.getBounds().getMaxY()) {
done = true;
} else if (startY < worldModel.getBounds().getMinY()) {
done = true;
} else {
totalDistance += distanceStep;
if (totalDistance > maxDistance) {
done = true;
}
}
}
}
} else {
rtree.intersects((float) ray.getOrigin().getX(), (float) ray
.getOrigin().getY(), (float) ray.getEndPoint().getX(),
(float) ray.getEndPoint().getY(),
closestBuildingIntProcedure);
}
if (saveRays
&& closestBuildingIntProcedure.getClosestBuildingIntersect() != null) {
rays.add(new Line2D(ray.getOrigin(),
ray.getPoint(closestBuildingIntProcedure
.getClosestIntersect())));
}
return closestBuildingIntProcedure.getClosestBuildingIntersect();
}
private double getLength(Edge edge) {
int diffX = edge.getStartX() - edge.getEndX();
int diffY = edge.getStartY() - edge.getEndY();
return Math.sqrt(diffX * diffX + diffY * diffY);
}
private Rectangle getBoundingBox(Building building) {
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
int[] apexes = building.getApexList();
for (int i = 0; i < apexes.length; i = i + 2) {
minX = Math.min(apexes[i], minX);
minY = Math.min(apexes[i + 1], minY);
maxX = Math.max(apexes[i], maxX);
maxY = Math.max(apexes[i + 1], maxY);
}
return new Rectangle(minX, minY, maxX, maxY);
}
public class ClosestBuildingIntProcedure implements IntProcedure {
private Line2D ray;
private Building closestBuildingIntersect;
private double closestIntersect = Double.MAX_VALUE;
private Building building;
public ClosestBuildingIntProcedure(Line2D ray, Building building) {
this.ray = ray;
this.building = building;
}
@Override
public boolean execute(int id) {
if (id == building.getID().getValue()) {
return true;
}
Building building = (Building) worldModel
.getEntity(new EntityID(id));
// the line hit the bounding box, test if it actually hit the
// building as well
Double x = getBuildingIntersect(ray, building);
if (x != null && x > 0 && x < closestIntersect) {
closestIntersect = x;
closestBuildingIntersect = building;
}
return true;
}
public Building getClosestBuildingIntersect() {
return closestBuildingIntersect;
}
public double getClosestIntersect() {
return closestIntersect;
}
}
public DirectedSparseGraph<Building, HeatTransferRelation> getGraph() {
return heatTransferGraph;
}
public Collection<Building> getNeighbouringBuildings(Building building) {
return heatTransferGraph.getNeighbors(building);
}
public Map<Building, Double> getHeatTransferCoefficients(Building building) {
Map<Building, Double> result = new FastMap<Building, Double>();
for (HeatTransferRelation edge : heatTransferGraph
.getOutEdges(building)) {
result.put(edge.getDestination(), edge.getHeatTransferRate());
}
return result;
}
public Collection<Building> getBuildings() {
return heatTransferGraph.getVertices();
}
public Map<Building, Double> getHeatTransferRays(Building building) {
Map<Building, Double> result = new FastMap<Building, Double>();
for (HeatTransferRelation edge : heatTransferGraph
.getOutEdges(building)) {
result.put(edge.getDestination(), (double) edge.getRays());
}
return result;
}
/**
* Computes the fraction at which the ray intersects the building. Returns
* null if the ray does not intersect the building
*
* @param ray
* @param building
* @return
*/
private Double getBuildingIntersect(Line2D ray, Building building) {
double rayStartX = ray.getOrigin().getX();
double rayStartY = ray.getOrigin().getY();
double rayEndX = ray.getEndPoint().getX();
double rayEndY = ray.getEndPoint().getY();
double closestIntersect = Double.MAX_VALUE;
// the line hit the bounding box, test if it hit the building?
for (Edge edge : building.getEdges()) {
// do the line *segments* intersect?
if (!java.awt.geom.Line2D.linesIntersect(rayStartX, rayStartY,
rayEndX, rayEndY, edge.getStartX(), edge.getStartY(), edge
.getEndX(), edge.getEndY()))
continue;
// if so, where? (computes line intersection, not *segment*
// intersection)
double x = ray.getIntersection(edge.getLine());
// getIntersection returns NaN in case of parallel lines
if (!Double.isNaN(x)) {
if (x > 0 && x < closestIntersect) {
closestIntersect = x;
}
}
}
if (closestIntersect == Double.MAX_VALUE)
return null;
return closestIntersect;
}
public DirectedSparseGraph<Building, HeatTransferRelation> getHeatTransferGraph() {
return heatTransferGraph;
}
public Collection<Line2D> getRays() {
return rays;
}
protected Object clone() throws CloneNotSupportedException {
HeatTransferGraph clone = (HeatTransferGraph) super.clone();
// TODO make clone of the heatTransferGraph
clone.heatTransferGraph = null;// (HeatTransferGraph)
// heatTransferGraph.clone();
return clone;
}
}