/*
Copyright 2006 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.portrayal.grid;
import sim.portrayal.*;
import sim.portrayal.simple.*;
import sim.field.grid.*;
import sim.util.*;
import java.awt.*;
import java.util.*;
import java.awt.geom.*;
import sim.portrayal.inspector.*;
/**
Can be used to draw both continuous and discrete sparse fields
*/
public class SparseGridPortrayal2D extends FieldPortrayal2D
{
public DrawPolicy policy;
public SparseGridPortrayal2D()
{
super();
}
public SparseGridPortrayal2D (DrawPolicy policy)
{
super();
this.policy = policy;
}
// a grey oval. You should provide your own protrayals...
SimplePortrayal2D defaultPortrayal = new OvalPortrayal2D();
public Portrayal getDefaultPortrayal()
{
return defaultPortrayal;
}
public void setField(Object field)
{
dirtyField = true;
if (field instanceof SparseGrid2D ) this.field = field;
else throw new RuntimeException("Invalid field for Sparse2DPortrayal: " + field);
}
public Int2D getLocation(DrawInfo2D info)
{
final Grid2D field = (Grid2D) this.field;
if (field==null) return null;
int maxX = field.getWidth();
int maxY = field.getHeight();
final double xScale = info.draw.width / maxX;
final double yScale = info.draw.height / maxY;
final int startx = (int)((info.clip.x - info.draw.x) / xScale);
final int starty = (int)((info.clip.y - info.draw.y) / yScale); // assume that the X coordinate is proportional -- and yes, it's _width_
return new Int2D(startx, starty);
}
public Point2D.Double getPositionInFieldPortrayal(Object object, DrawInfo2D info)
{
final SparseGrid2D field = (SparseGrid2D) this.field;
if (field==null) return null;
int maxX = field.getWidth();
int maxY = field.getHeight();
final double xScale = info.draw.width / maxX;
final double yScale = info.draw.height / maxY;
DrawInfo2D newinfo = new DrawInfo2D(new Rectangle2D.Double(0,0, xScale, yScale), info.clip);
Int2D loc = field.getObjectLocation(object);
if (loc == null) return null;
// translate --- the + newinfo.width/2.0 etc. moves us to the center of the object
newinfo.draw.x = (int)(info.draw.x + (xScale) * loc.x);
newinfo.draw.y = (int)(info.draw.y + (yScale) * loc.y);
newinfo.draw.width = (int)(info.draw.x + (xScale) * (loc.x+1)) - newinfo.draw.x;
newinfo.draw.height = (int)(info.draw.y + (yScale) * (loc.y+1)) - newinfo.draw.y;
// adjust drawX and drawY to center
newinfo.draw.x += newinfo.draw.width / 2.0;
newinfo.draw.y += newinfo.draw.height / 2.0;
return new Point2D.Double(newinfo.draw.x, newinfo.draw.y);
}
protected void hitOrDraw(Graphics2D graphics, DrawInfo2D info, Bag putInHere)
{
final SparseGrid2D field = (SparseGrid2D) this.field;
if (field==null) return;
boolean objectSelected = !selectedWrappers.isEmpty();
int maxX = field.getWidth();
int maxY = field.getHeight();
final double xScale = info.draw.width / maxX;
final double yScale = info.draw.height / maxY;
final int startx = (int)((info.clip.x - info.draw.x) / xScale);
final int starty = (int)((info.clip.y - info.draw.y) / yScale); // assume that the X coordinate is proportional -- and yes, it's _width_
int endx = /*startx +*/ (int)((info.clip.x - info.draw.x + info.clip.width) / xScale) + /*2*/ 1; // with rounding, width be as much as 1 off
int endy = /*starty +*/ (int)((info.clip.y - info.draw.y + info.clip.height) / yScale) + /*2*/ 1; // with rounding, height be as much as 1 off
final Rectangle clip = (graphics==null ? null : graphics.getClipBounds());
DrawInfo2D newinfo = new DrawInfo2D(new Rectangle2D.Double(0,0, xScale, yScale),
info.clip); // we don't do further clipping
// If the person has specified a policy, we have to iterate through the
// bags. At present we have to do this by using a hash table iterator
// (yuck -- possibly expensive, have to search through empty locations).
//
// We never use the policy to determine hitting. hence this only works if graphics != null
if (policy != null && graphics != null)
{
Bag policyBag = new Bag();
Iterator iterator = field.locationBagIterator();
while(iterator.hasNext())
{
Bag objects = (Bag)(iterator.next());
if (objects == null) continue;
// restrict the number of objects to draw
policyBag.clear(); // fast
if (policy.objectToDraw(objects,policyBag)) // if this function returns FALSE, we should use objects as is, else use the policy bag.
objects = policyBag; // returned TRUE, so we're going to use the modified policyBag instead.
// draw 'em
for(int x=0;x<objects.numObjs;x++)
{
final Object portrayedObject = objects.objs[x];
Int2D loc = field.getObjectLocation(portrayedObject);
// here we only draw the object if it's within our range. However objects
// might leak over to other places, so I dunno... I give them the benefit
// of the doubt that they might be three times the size they oughta be, hence the -2 and +2's
if (loc.x >= startx -2 && loc.x < endx + 4 &&
loc.y >= starty -2 && loc.y < endy + 4)
{
Portrayal p = getPortrayalForObject(portrayedObject);
if (!(p instanceof SimplePortrayal2D))
throw new RuntimeException("Unexpected Portrayal " + p + " for object " +
portrayedObject + " -- expected a SimplePortrayal2D");
SimplePortrayal2D portrayal = (SimplePortrayal2D) p;
// translate --- the + newinfo.width/2.0 etc. moves us to the center of the object
newinfo.draw.x = (int)(info.draw.x + (xScale) * loc.x);
newinfo.draw.y = (int)(info.draw.y + (yScale) * loc.y);
newinfo.draw.width = (int)(info.draw.x + (xScale) * (loc.x+1)) - newinfo.draw.x;
newinfo.draw.height = (int)(info.draw.y + (yScale) * (loc.y+1)) - newinfo.draw.y;
// adjust drawX and drawY to center
newinfo.draw.x += newinfo.draw.width / 2.0;
newinfo.draw.y += newinfo.draw.height / 2.0;
if (objectSelected && // there's something there
selectedWrappers.get(portrayedObject) != null)
{
LocationWrapper wrapper = (LocationWrapper)(selectedWrappers.get(portrayedObject));
portrayal.setSelected(wrapper,true);
portrayal.draw(portrayedObject, graphics, newinfo);
portrayal.setSelected(wrapper,false);
}
else portrayal.draw(portrayedObject, graphics, newinfo);
}
}
}
}
else // the easy way -- draw the objects one by one
{
Bag objects = field.getAllObjects();
for(int x=0;x<objects.numObjs;x++)
{
final Object portrayedObject = objects.objs[x];
Int2D loc = field.getObjectLocation(portrayedObject);
// here we only draw the object if it's within our range. However objects
// might leak over to other places, so I dunno... I give them the benefit
// of the doubt that they might be three times the size they oughta be, hence the -2 and +2's
if (loc.x >= startx -2 && loc.x < endx + 4 &&
loc.y >= starty -2 && loc.y < endy + 4)
{
Portrayal p = getPortrayalForObject(portrayedObject);
if (!(p instanceof SimplePortrayal2D))
throw new RuntimeException("Unexpected Portrayal " + p + " for object " +
portrayedObject + " -- expected a SimplePortrayal2D");
SimplePortrayal2D portrayal = (SimplePortrayal2D) p;
// translate --- the + newinfo.width/2.0 etc. moves us to the center of the object
newinfo.draw.x = (int)(info.draw.x + (xScale) * loc.x);
newinfo.draw.y = (int)(info.draw.y + (yScale) * loc.y);
newinfo.draw.width = (int)(info.draw.x + (xScale) * (loc.x+1)) - newinfo.draw.x;
newinfo.draw.height = (int)(info.draw.y + (yScale) * (loc.y+1)) - newinfo.draw.y;
// adjust drawX and drawY to center
newinfo.draw.x += newinfo.draw.width / 2.0;
newinfo.draw.y += newinfo.draw.height / 2.0;
if (graphics == null)
{
if (portrayal.hitObject(portrayedObject, newinfo))
putInHere.add(getWrapper(portrayedObject));
}
else
{
// MacOS X 10.3 Panther has a bug which resets the clip, YUCK
// graphics.setClip(clip);
if (objectSelected && // there's something there
selectedWrappers.get(portrayedObject) != null)
{
LocationWrapper wrapper = (LocationWrapper)(selectedWrappers.get(portrayedObject));
portrayal.setSelected(wrapper,true);
portrayal.draw(portrayedObject, graphics, newinfo);
portrayal.setSelected(wrapper,false);
}
else portrayal.draw(portrayedObject, graphics, newinfo);
}
}
}
}
}
// The easiest way to make an inspector which gives the location of my objects
public LocationWrapper getWrapper(Object object)
{
final SparseGrid2D field = (SparseGrid2D) this.field;
final StableInt2D w = new StableInt2D(field, object);
return new LocationWrapper( object, null, this ) // don't care about location
{
public Object getLocation()
{
w.update();
return w;
}
public String getLocationName()
{
w.update();
return w.toString();
}
};
}
HashMap selectedWrappers = new HashMap();
public boolean setSelected(LocationWrapper wrapper, boolean selected)
{
if (wrapper == null) return true;
if (wrapper.getFieldPortrayal() != this) return true;
Object obj = wrapper.getObject();
if (selected)
{
// first let's determine if the object WANTs to be selected
boolean b = getPortrayalForObject(obj).setSelected(wrapper,selected);
// now we turn the selection back to regular
getPortrayalForObject(obj).setSelected(wrapper,!selected);
// Okay, now we can tell whether or not to add to the wrapper collection
if (b==false) return false;
selectedWrappers.put(obj, wrapper);
}
else
{
selectedWrappers.remove(obj);
}
return true;
}
}