/*
* 作成日: 2008/04/24
*/
package jp.ac.fit.asura.nao.localization;
import static jp.ac.fit.asura.nao.misc.MathUtils.normalizeAngle180;
import static jp.ac.fit.asura.nao.misc.MathUtils.normalizeAnglePI;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Map;
import javax.vecmath.Point2f;
import jp.ac.fit.asura.nao.FrameContext;
import jp.ac.fit.asura.nao.RobotContext;
import jp.ac.fit.asura.nao.VisualCycle;
import jp.ac.fit.asura.nao.VisualFrameContext;
import jp.ac.fit.asura.nao.event.MotionEventListener;
import jp.ac.fit.asura.nao.event.VisualEventListener;
import jp.ac.fit.asura.nao.localization.self.MonteCarloLocalization;
import jp.ac.fit.asura.nao.localization.self.SelfLocalization;
import jp.ac.fit.asura.nao.misc.MathUtils;
import jp.ac.fit.asura.nao.motion.Motion;
import jp.ac.fit.asura.nao.strategy.Team;
import jp.ac.fit.asura.nao.vision.VisualContext;
import jp.ac.fit.asura.nao.vision.VisualObjects;
import jp.ac.fit.asura.nao.vision.perception.BallVisualObject;
import jp.ac.fit.asura.nao.vision.perception.GoalVisualObject;
import jp.ac.fit.asura.nao.vision.perception.RobotVisualObject;
import jp.ac.fit.asura.nao.vision.perception.VisualObject;
import org.apache.log4j.Logger;
/**
* @author sey
*
* @version $Id: Localization.java 717 2008-12-31 18:16:20Z sey $
*
*/
public class Localization implements VisualCycle, MotionEventListener,
VisualEventListener {
private static final Logger log = Logger.getLogger(Localization.class);
private SelfLocalization self;
private Map<WorldObjects, WorldObject> worldObjects;
private WorldObject woSelf;
private RobotContext context;
private FrameContext frame;
private float odometryForward;
private float odometryLeft;
private float odometryTurn;
public Localization() {
worldObjects = new HashMap<WorldObjects, WorldObject>();
for (WorldObjects e : WorldObjects.values())
worldObjects.put(e, new WorldObject(e));
self = new MonteCarloLocalization();
// self = new GPSLocalization();
// self = new OdometryLocalization();
woSelf = worldObjects.get(WorldObjects.Self);
}
@Override
public void init(RobotContext rctx) {
this.context = rctx;
self.init(rctx);
context.getMotor().addEventListener(this);
context.getVision().addEventListener(this);
}
@Override
public void start() {
self.start();
}
@Override
public void step(VisualFrameContext context) {
self.step(context);
}
@Override
public void updateVision(VisualContext vc) {
updateOdometryBatch();
frame = vc.getFrameContext();
self.updateVision(vc);
mapSelf();
updateVisualObject(worldObjects.get(WorldObjects.Ball), vc
.get(VisualObjects.Ball));
updateVisualObject(worldObjects.get(WorldObjects.BlueGoal), vc
.get(VisualObjects.BlueGoal));
updateVisualObject(worldObjects.get(WorldObjects.YellowGoal), vc
.get(VisualObjects.YellowGoal));
updateVisualObject(worldObjects.get(WorldObjects.RedNao), vc
.get(VisualObjects.RedNao));
updateVisualObject(worldObjects.get(WorldObjects.BlueNao), vc
.get(VisualObjects.BlueNao));
boolean isRed = context.getStrategy().getTeam() == Team.Red;
for (WorldObject wo : worldObjects.values())
copyWorldToTeamCoord(wo, isRed);
frame = null;
}
@Override
public void stop() {
self.stop();
}
public WorldObject get(WorldObjects wo) {
return worldObjects.get(wo);
}
/**
* {@link SelfLocalization}からWorldObjectに自己位置情報をコピーします.
*/
private void mapSelf() {
WorldObject wo = worldObjects.get(WorldObjects.Self);
wo.world.x = self.getX();
wo.world.y = self.getY();
wo.worldYaw = MathUtils.toDegrees(self.getHeading());
wo.worldAngle = calcWorldAngle(wo);
wo.cf = self.getConfidence();
wo.dist = 0;
wo.heading = 0;
}
/**
* {@link VisualObject}の情報に基づいて{@link WorldObject}の情報を更新します.
*
* @param vo
*/
private void updateVisualObject(WorldObject wo, VisualObject vo) {
boolean distUsable = false;
int dist;
if (vo.getType() == VisualObjects.YellowGoal) {
distUsable = ((GoalVisualObject) vo).distanceUsable;
dist = ((GoalVisualObject) vo).distance;
} else if (vo.getType() == VisualObjects.BlueGoal) {
distUsable = ((GoalVisualObject) vo).distanceUsable;
dist = ((GoalVisualObject) vo).distance;
} else if (vo.getType() == VisualObjects.Ball) {
distUsable = ((BallVisualObject) vo).distanceUsable;
dist = ((BallVisualObject) vo).distance;
} else if (vo.getType() == VisualObjects.RedNao) {
distUsable = ((RobotVisualObject) vo).distanceUsable;
dist = ((RobotVisualObject) vo).distance;
} else if (vo.getType() == VisualObjects.BlueNao) {
distUsable = ((RobotVisualObject) vo).distanceUsable;
dist = ((RobotVisualObject) vo).distance;
} else {
assert false;
return;
}
wo.setVision(vo);
int voCf = vo.confidence;
// find ball coordinate
// WMObject を更新
// ボールが見えていれば
if (voCf > 0 && distUsable) {
Point2f angle = vo.robotAngle;
float head = MathUtils.toDegrees(angle.x);
// filter
dist = wo.distFilter.eval(dist);
head = wo.headingFilter.eval(head);
float woHead = woSelf.worldYaw + head;
float rad = MathUtils.toRadians(woHead);
wo.world.x = woSelf.world.x + (int) (dist * MathUtils.sin(rad));
wo.world.y = woSelf.world.y + (int) (dist * MathUtils.cos(rad));
float rate = (wo.cf >= 600) ? 0.3f : 1.0f;
float curFr = voCf * rate / (voCf + wo.cf);
float ballFr = 1 - curFr;
wo.cf = (int) (ballFr * wo.cf + curFr * voCf);
// wo.cf = (int) (wo.cf * 0.8 + voCf * 0.2);
wo.dist = (int) dist;
wo.heading = head;
wo.lasttime = frame.getTime();
wo.worldAngle = calcWorldAngle(wo);
} else {
// 入力なし
wo.distFilter.eval();
wo.headingFilter.eval();
// 信頼度を下げておく
//wo.cf *= wo.dist > 500 ? 0.98f : 0.998f;
//wo.cf *= wo.dist > 500 ? 0.85f : 0.95f;
wo.cf *= 0.7;
// wo.cf *= 0.99;
// wmballのcfがゼロでなければ
// 自己位置の修正を考慮してボール位置を再計算
if (wo.cf > 0) {
// ここでは自己位置の修正によってボールとの距離・角度は変化しないと仮定
updatePosition(wo);
}
}
// 最後に認識した時からの経過時間を計算
wo.difftime = frame.getTime() - wo.lasttime;
log.trace("updateVisualObject: " + wo);
}
@Override
public synchronized void updateOdometry(float forward, float left,
float turnCCW) {
odometryLeft += MathUtils.sin(odometryTurn) * forward
+ MathUtils.cos(odometryTurn) * left;
odometryForward += MathUtils.cos(odometryTurn) * forward
- MathUtils.sin(odometryTurn) * left;
odometryTurn = normalizeAnglePI(odometryTurn + turnCCW);
}
public void updatePosture() {
self.updatePosture();
}
public void startMotion(Motion motion) {
self.startMotion(motion);
}
public void stopMotion(Motion motion) {
self.stopMotion(motion);
}
/**
* オドメトリの更新2
*/
private void updateOdometryBatch() {
float forward;
float left;
float turnCCW;
synchronized (this) {
forward = odometryForward;
left = odometryLeft;
turnCCW = odometryTurn;
odometryForward = 0;
odometryLeft = 0;
odometryTurn = 0;
}
if (log.isTraceEnabled()) {
NumberFormat f = NumberFormat.getNumberInstance();
f.setMaximumFractionDigits(2);
log.trace("updateOdometry " + f.format(forward) + ", "
+ f.format(left) + ", " + f.format(turnCCW));
}
self.updateOdometry(forward, left, turnCCW);
mapSelf();
// updateRelativePosition(worldObjects.get(WorldObjects.Ball));
// updateRelativePosition(worldObjects.get(WorldObjects.YellowGoal));
// updateRelativePosition(worldObjects.get(WorldObjects.BlueGoal));
}
/**
* WorldObject woと自己位置から距離と角度の情報を更新します.
*
* {@link #updatePosition(WorldObject)}では距離と角度の情報を保存しWorldX/Yを更新するのに対し、
* ここではWorldX/Yの情報を保存し距離と角度の情報を更新します.
*
* @param wo
* 更新するWorldObject
*/
private void updateRelativePosition(WorldObject wo) {
// 得られた ball の x, y を元に角度と距離を計算する
// caculate ball's info relative to robot's position
// double angle = RAD2DEG(asin(abs(ballY-ary)/dist));
float angle = MathUtils.toDegrees(MathUtils.atan2(wo.world.x
- woSelf.world.x, wo.world.y - woSelf.world.y));
wo.dist = wo.distFilter.eval((int) MathUtils.distance(wo.world,
woSelf.world));
wo.heading = wo.headingFilter.eval(normalizeAngle180(angle
- woSelf.worldYaw));
}
/**
* WorldObject woと自己位置からWorldXとWorldYの情報を更新します.
*
* {@link #updateRelativePosition(WorldObject)}
* ではWorldX/Yの情報を保存し距離と角度を更新するのに対し、 ここでは距離と角度の情報を保存しWorldX/Yの情報を更新します.
*
* @param wo
* 更新するWorldObject
*/
private void updatePosition(WorldObject wo) {
float rad = MathUtils.toRadians(woSelf.worldYaw + wo.heading);
wo.world.x = woSelf.world.x + (int) (wo.dist * MathUtils.sin(rad));
wo.world.y = woSelf.world.y + (int) (wo.dist * MathUtils.cos(rad));
wo.worldAngle = calcWorldAngle(wo);
}
private float calcWorldAngle(WorldObject wo) {
return MathUtils.toDegrees(MathUtils.atan2(wo.world.x, wo.world.y));
}
/**
* @return the selfLocalization
*/
public SelfLocalization getSelf() {
return self;
}
private void copyWorldToTeamCoord(WorldObject wo, boolean isRed) {
if (isRed) {
wo.teamAngle = wo.worldAngle;
wo.teamYaw = wo.worldYaw;
wo.team.x = wo.world.x;
wo.team.y = wo.world.y;
} else {
wo.teamAngle = normalizeAngle180(wo.worldAngle - 180);
wo.teamYaw = normalizeAngle180(wo.worldYaw - 180);
wo.team.x = -wo.world.x;
wo.team.y = -wo.world.y;
}
}
}