/*
* Javlov - a Java toolkit for reinforcement learning with multi-agent support.
*
* Copyright (c) 2009 Matthijs Snel
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.javlov.world;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.List;
import net.javlov.VectorState;
/**
* Sensor that manages a group of child sensors. The child sensors need to implement the
* GroupedSensor interface in order to be able to produce a reading from a list of predefined
* objects. The group of objects that the children read from is prefiltered from the world by the
* CompositeSensor. In addition the sensor only modifies the position and rotation of its children
* when necessary, i.e. when there are objects in range and the children need to produce a reading.
*
* NOTE: this class doesn't produce a reading if there are less than 2 bodies within range; this
* is because DistanceSensors always detect the agent's own body. And this class has only been tested
* with DistanceSensors.
*
* @author Matthijs Snel
*
*/
public class CompositeSensor implements PhysicalSensor {
private int numChildren;
protected List<GroupedSensor> children;
protected World world;
protected Path2D.Double rangeArea;
private double totaldx, totaldy, totalRot, lastCentreX, lastCentreY;
public CompositeSensor() {
children = new ArrayList<GroupedSensor>();
rangeArea = new Path2D.Double();
}
public CompositeSensor(World world) {
this();
setWorld(world);
}
public void add(GroupedSensor s) {
children.add(s);
rangeArea.append(s.getRangeArea(), false);
}
public void setWorld(World w) {
world = w;
}
@Override
public void rotate(double angle) {
for ( GroupedSensor s : children )
s.rotate(angle);
reCalcRangeArea();
}
@Override
public void rotate(double angle, double x, double y) {
totalRot += angle;
lastCentreX = x;
lastCentreY = y;
rangeArea.transform(AffineTransform.getRotateInstance(angle, x, y));
}
@Override
public void setBody(Body b) {
//not needed
}
@Override
public void translate(double dx, double dy) {
totaldx += dx;
totaldy += dy;
rangeArea.transform(AffineTransform.getTranslateInstance(dx, dy));
}
@Override
public double[] getReading() {
List<Body> objectsInRange = world.getIntersectingObjects(rangeArea.getBounds2D());
//TODO dangerous assumption: own body is always in range for distance sensors,
//but might not be for other sensors
if ( objectsInRange.size() < 2 )
return new double[numChildren];
VectorState st = new VectorState();
for ( GroupedSensor gs : children ) {
gs.translate(totaldx, totaldy);
gs.rotate(totalRot, lastCentreX, lastCentreY);
st.append(gs.getReadingFromObjects(objectsInRange));
}
totalRot = totaldx = totaldy = 0;
return st.getData();
}
@Override
public void init() {
for ( GroupedSensor s : children )
s.init();
//assume no more sensors added
numChildren = children.size();
}
@Override
public void reset() {
for ( GroupedSensor s : children )
s.reset();
numChildren = children.size();
}
private void reCalcRangeArea() {
rangeArea.reset();
for ( GroupedSensor s : children )
rangeArea.append(s.getRangeArea(), false);
}
@Override
public Shape getRangeArea() {
return rangeArea;
}
@Override
public int getReadingDim() {
int dim = 0;
for ( GroupedSensor gs : children )
dim += gs.getReadingDim();
return dim;
}
}