/*
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.*;
import sim.display.*;
/**
Can be used to draw both continuous and discrete sparse fields.
The 'location' passed
into the DrawInfo2D handed to the SimplePortryal2D is an Int3D.
*/
public class SparseGrid3DPortrayal2D extends SparseGridPortrayal2D
{
public void setField(Object field)
{
if (field instanceof SparseGrid3D ) setFieldBypass(field);
else throw new RuntimeException("Invalid field for SparseGrid3DPortrayal2D: " + field);
}
public Double2D getScale(DrawInfo2D info)
{
synchronized(info.gui.state.schedule)
{
final Grid3D field = (Grid3D) 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;
return new Double2D(xScale, yScale);
}
}
/** Returns the location corresponding with the given position -- and assuming that the
location has a z-value of 0. */
public Object getPositionLocation(Point2D.Double position, DrawInfo2D info)
{
Double2D scale = getScale(info);
double xScale = scale.x;
double yScale = scale.y;
final int startx = (int)Math.floor((position.getX() - info.draw.x) / xScale);
final int starty = (int)Math.floor((position.getY() - info.draw.y) / yScale); // assume that the X coordinate is proportional -- and yes, it's _width_
return new Int3D(startx, starty, 0);
}
public void setObjectLocation(Object object, Object location, GUIState gui)
{
synchronized(gui.state.schedule)
{
if (location != null)
{
if (location instanceof Int3D)
{
Int3D loc = (Int3D) location;
if (object instanceof Fixed2D && (!((Fixed2D)object).maySetLocation(field, loc)))
return; // this is deprecated and will be deleted
else if (object instanceof Constrained)
loc = (Int3D)((Constrained)object).constrainLocation(field, loc);
if (loc != null)
((SparseGrid3D)field).setObjectLocation(object, loc);
}
}
}
}
/*
public void setObjectPosition(Object object, Point2D.Double position, DrawInfo2D fieldPortrayalInfo)
{
synchronized(fieldPortrayalInfo.gui.state.schedule)
{
final SparseGrid3D field = (SparseGrid3D)this.field;
if (field==null) return;
Int3D oldLocation = (Int3D)(field.getObjectLocation(object));
if (oldLocation == null) return;
Int3D location = (Int3D)(getPositionLocation(position, fieldPortrayalInfo));
if (location != null)
{
// since getPositionLocation assumes a z-value of 1, we set the location to the proper z
location = new Int3D(location.x, location.y, oldLocation.z);
if (object instanceof Fixed2D && (!((Fixed2D)object).maySetLocation(field, location)))
return;
else if (object instanceof Constrained)
location = (Int3D)((Constrained)object).constrainLocation(field, location);
if (location != null)
field.setObjectLocation(object, location);
}
}
}
*/
public Object getObjectLocation(Object object, GUIState gui)
{
synchronized(gui.state.schedule)
{
final SparseGrid3D field = (SparseGrid3D)this.field;
if (field==null) return null;
return field.getObjectLocation(object);
}
}
public Point2D.Double getLocationPosition(Object location, DrawInfo2D info)
{
synchronized(info.gui.state.schedule)
{
final Grid3D field = (Grid3D) 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(info.gui, info.fieldPortrayal, new Rectangle2D.Double(0,0, xScale, yScale), info.clip, info);
newinfo.precise = info.precise;
Int3D loc = (Int3D)location;
if (loc == null) return null;
// translate --- the + newinfo.width/2.0 etc. moves us to the center of the object
newinfo.draw.x = (int)Math.floor(info.draw.x + (xScale) * loc.x);
newinfo.draw.y = (int)Math.floor(info.draw.y + (yScale) * loc.y);
newinfo.draw.width = (int)Math.floor(info.draw.x + (xScale) * (loc.x+1)) - newinfo.draw.x;
newinfo.draw.height = (int)Math.floor(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);
}
}
//// FIXME: The computational complexity of this could be improved. At present
//// we are sorting everything by Z and then throwing out the stuff that doesn't
//// fall within the drawing region. Instead, we should gather all the elements
//// that fall within the region and THEN sort them by Z.
//// See also Continuous3DPortrayal2D
protected void hitOrDraw(Graphics2D graphics, DrawInfo2D info, Bag putInHere)
{
final SparseGrid3D field = (SparseGrid3D) 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)Math.floor((info.clip.x - info.draw.x) / xScale);
final int starty = (int)Math.floor((info.clip.y - info.draw.y) / yScale); // assume that the X coordinate is proportional -- and yes, it's _width_
int endx = /*startx +*/ (int)Math.floor((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)Math.floor((info.clip.y - info.draw.y + info.clip.height) / yScale) + /*2*/ 1; // with rounding, height be as much as 1 off
DrawInfo2D newinfo = new DrawInfo2D(info.gui, info.fieldPortrayal, new Rectangle2D.Double(0,0, xScale, yScale), info.clip, info); // we don't do further clipping
newinfo.precise = info.precise;
newinfo.fieldPortrayal = this;
// 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)
{
Iterator iterator = field.locationBagIterator();
Bag bagbag = new Bag();
while(iterator.hasNext())
{
Bag objects = (Bag)(iterator.next());
if (objects == null || objects.size() == 0) continue;
bagbag.add(objects);
}
// at this point we have a bag of BAGS, where each sub-bag is the objects at
// a given location. So we use a comparator which sorts the sub-bags based
// on (say) the first element in each of them.
// sort here
bagbag.sort(new Comparator()
{
public int compare(Object o1, Object o2)
{
Bag b1 = (Bag)o1;
Bag b2 = (Bag)o2;
Int3D i1 = (Int3D)(field.getObjectLocation(b1.get(0)));
Int3D i2 = (Int3D)(field.getObjectLocation(b2.get(0)));
// sort so that smaller objects appear first
if (i1.z < i2.z) return -1;
if (i2.z < i1.z) return 1;
return 0;
}
});
// now we apply the policy
Bag policyBag = new Bag();
for(int i = 0; i < bagbag.size(); i++)
{
Bag objects = (Bag)(bagbag.get(i));
// 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];
Int3D 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)Math.floor(info.draw.x + (xScale) * loc.x);
newinfo.draw.y = (int)Math.floor(info.draw.y + (yScale) * loc.y);
newinfo.draw.width = (int)Math.floor(info.draw.x + (xScale) * (loc.x+1)) - newinfo.draw.x;
newinfo.draw.height = (int)Math.floor(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;
newinfo.location = loc;
newinfo.selected = (objectSelected && // there's something there
selectedWrappers.get(portrayedObject) != null);
portrayal.draw(portrayedObject, graphics, newinfo);
}
}
}
}
else // the easy way -- draw the objects one by one
{
Bag objects = new Bag(field.getAllObjects()); // copy the bag
objects.sort(new Comparator()
{
public int compare(Object o1, Object o2)
{
Int3D i1 = (Int3D)(field.getObjectLocation(o1));
Int3D i2 = (Int3D)(field.getObjectLocation(o2));
// sort so that smaller objects appear first
if (i1.z < i2.z) return -1;
if (i2.z < i1.z) return 1;
return 0;
}
});
for(int x=0;x<objects.numObjs;x++)
{
final Object portrayedObject = objects.objs[x];
Int3D 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)Math.floor(info.draw.x + (xScale) * loc.x);
newinfo.draw.y = (int)Math.floor(info.draw.y + (yScale) * loc.y);
newinfo.draw.width = (int)Math.floor(info.draw.x + (xScale) * (loc.x+1)) - newinfo.draw.x;
newinfo.draw.height = (int)Math.floor(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, newinfo.gui));
}
}
else
{
newinfo.selected = (objectSelected && // there's something there
selectedWrappers.get(portrayedObject) != null);
portrayal.draw(portrayedObject, graphics, newinfo);
}
}
}
}
drawGrid(graphics, xScale, yScale, maxX, maxY, info);
drawBorder(graphics, xScale, info);
}
// The easiest way to make an inspector which gives the location of my objects
public LocationWrapper getWrapper(Object object, GUIState gui)
{
final SparseGrid3D field = (SparseGrid3D) this.field;
final StableInt3D w = new StableInt3D(this, object, gui);
return new LocationWrapper( object, null, this ) // don't care about location
{
public Object getLocation()
{
return w;
}
public String getLocationName()
{
return w.toString();
}
};
}
}